[Mulgara-svn] r691 - in trunk: . docs/site-src/design jxdata/iTQL/data_types jxdata/iTQL/standard_queries lib src/jar/itql/java/org/mulgara/itql src/jar/krule/java/org/mulgara/krule src/jar/query/java/org/mulgara/query src/jar/query/java/org/mulgara/server src/jar/resolver src/jar/resolver/java/org/mulgara/resolver src/jar/resolver-distributed/java/org/mulgara/resolver/distributed src/jar/resolver-memory/java/org/mulgara/resolver/memory src/jar/resolver-spi/java/org/mulgara/resolver/spi src/jar/resolver-store/java/org/mulgara/resolver/store src/jar/resolver-test/java/org/mulgara/resolver/test src/jar/resolver-view/java/org/mulgara/resolver/view src/jar/server-beep/java/org/mulgara/server/beep src/jar/server-rmi/java/org/mulgara/server/rmi src/jar/util/java/org/mulgara/util

andrae at mulgara.org andrae at mulgara.org
Tue Mar 18 09:32:15 UTC 2008


Author: andrae
Date: 2008-03-18 02:32:14 -0700 (Tue, 18 Mar 2008)
New Revision: 691

Added:
   trunk/docs/site-src/design/JTAInterface.txt
   trunk/lib/carol-2.0.5.jar
   trunk/lib/howl-logger-0.1.11.jar
   trunk/lib/jotm-2.0.10.jar
   trunk/lib/jotm_jrmp_stubs-2.0.10.jar
   trunk/src/jar/query/java/org/mulgara/query/SelectElement.java
   trunk/src/jar/query/java/org/mulgara/server/ResourceManagerInstanceAdaptor.java
   trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/AbstractXAResource.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransactionFactory.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransaction.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransactionFactory.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java
   trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResource.java
   trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResourceWrapperXAResource.java
   trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/XAResourceWrapperRemoteXAResource.java
   trunk/src/jar/util/java/org/mulgara/util/Assoc1toNMap.java
Removed:
   trunk/lib/jotm-1.5.3-patched.jar
   trunk/lib/jotm_carol-1.5.3.jar
   trunk/lib/jotm_iiop_stubs-1.5.3.jar
Modified:
   trunk/.classpath
   trunk/LEGAL.txt
   trunk/build.properties
   trunk/build.sh
   trunk/build.xml
   trunk/common.xml
   trunk/jxdata/iTQL/data_types/queryResult34.txt
   trunk/jxdata/iTQL/standard_queries/queryResult17.txt
   trunk/src/jar/itql/java/org/mulgara/itql/TqlInterpreter.java
   trunk/src/jar/itql/java/org/mulgara/itql/VariableBuilder.java
   trunk/src/jar/krule/java/org/mulgara/krule/QueryStruct.java
   trunk/src/jar/query/java/org/mulgara/query/AggregateFunction.java
   trunk/src/jar/query/java/org/mulgara/query/ConstantValue.java
   trunk/src/jar/query/java/org/mulgara/query/Query.java
   trunk/src/jar/query/java/org/mulgara/query/QueryUnitTest.java
   trunk/src/jar/query/java/org/mulgara/query/Variable.java
   trunk/src/jar/query/java/org/mulgara/server/Session.java
   trunk/src/jar/resolver-distributed/java/org/mulgara/resolver/distributed/NetworkDelegator.java
   trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolver.java
   trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolverFactory.java
   trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryXAResource.java
   trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/DummyXAResource.java
   trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/TripleSetWrapperStatements.java
   trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolver.java
   trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolverFactory.java
   trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreXAResource.java
   trunk/src/jar/resolver-test/java/org/mulgara/resolver/test/TestResolverUnitTest.java
   trunk/src/jar/resolver-view/java/org/mulgara/resolver/view/ViewResolverUnitTest.java
   trunk/src/jar/resolver/build.xml
   trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionListQueryUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransaction.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/XADatabaseSessionUnitTest.java
   trunk/src/jar/server-beep/java/org/mulgara/server/beep/BEEPSession.java
   trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSession.java
   trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSessionWrapperSession.java
   trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/SessionWrapperRemoteSession.java
Log:
refs #73
refs #85

Final merge of the JTA development into trunk.



Modified: trunk/.classpath
===================================================================
--- trunk/.classpath	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/.classpath	2008-03-18 09:32:14 UTC (rev 691)
@@ -86,9 +86,10 @@
 	<classpathentry kind="lib" path="lib/jsr173_07_ri.jar"/>
 	<classpathentry kind="lib" path="lib/jsr173_07_api.jar"/>
 	<classpathentry kind="lib" path="lib/js-1.5r3.jar"/>
-	<classpathentry kind="lib" path="lib/jotm-1.5.3-patched.jar"/>
-	<classpathentry kind="lib" path="lib/jotm_iiop_stubs-1.5.3.jar"/>
-	<classpathentry kind="lib" path="lib/jotm_carol-1.5.3.jar"/>
+	<classpathentry kind="lib" path="lib/jotm-2.0.10.jar"/>
+	<classpathentry kind="lib" path="lib/jotm_jrmp_stubs-2.0.10.jar"/>
+	<classpathentry kind="lib" path="lib/carol-2.0.5.jar"/>
+	<classpathentry kind="lib" path="lib/howl-logger-0.1.11.jar"/>
 	<classpathentry kind="lib" path="lib/jl1.0.jar"/>
 	<classpathentry kind="lib" path="lib/jid3-0.34.jar"/>
 	<classpathentry kind="lib" path="lib/jboss-j2ee.jar"/>

Modified: trunk/LEGAL.txt
===================================================================
--- trunk/LEGAL.txt	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/LEGAL.txt	2008-03-18 09:32:14 UTC (rev 691)
@@ -202,15 +202,36 @@
               http://www.gnu.org/copyleft/lesser.html
     
     JOTM (Java Open Transaction Manager)
-    JARs:     jotm-1.5.3.jar (locally patched)
-              jotm_carol-1.5.3.jar
-              jotm_iiop_stubs-1.5.3.jar
-              connector-1_5.jar (J2EE spec from Sun)
-              jta-spec1_0_1.jar (J2EE spec from Sun)
+    JARs:     jotm-2.0.10.jar
+              jotm_jrmp_stubs-2.0.10.jar
     URL:      http://jotm.objectweb.org/ 
     LICENSE:  BSD-style
               http://jotm.objectweb.org/bsd_license.html
+    
+    CAROL (Common Architecture for RMI ObjectWeb Layer)
+    JARs:     carol-2.0.5.jar
+    URL:      http://carol.objectweb.org/ 
+    LICENSE:  LGPL
+              http://www.gnu.org/copyleft/lesser.html
 
+    HOWL (High-speed ObjectWeb Logger)
+    JARs:     howl-logger-0.1.11.jar
+    URL:      http://howl.objectweb.org/ 
+    LICENSE:  Bull S.A.
+              http://howl.objectweb.org/license.html
+
+    Java Transaction API
+    JARs:     jta-spec1_0_1.jar
+    URL:      http://java.sun.com/products/jta
+    License:  Common Development and Distribution License (CDDL) v1.0
+              http://www.sun.com/cddl/cddl.html
+
+    JavaEE Connector Architecture (JCA)
+    JARs:     connector-1_5.jar (J2EE spec from Sun)
+    URL:      http://java.sun.com/products/jta
+    License:  Common Development and Distribution License (CDDL) v1.0
+              http://www.sun.com/cddl/cddl.html
+
     JRDF
     JARs:     jrdf-0.3.4.3.jar
     URL:      http://mulgara.org/files/jrdf/

Modified: trunk/build.properties
===================================================================
--- trunk/build.properties	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/build.properties	2008-03-18 09:32:14 UTC (rev 691)
@@ -129,9 +129,10 @@
 jsr.173.api.jar          =jsr173_07_api.jar
 jsr.173.ri.jar           =jsr173_07_ri.jar
 jta.jar                  =jta-spec1_0_1.jar
-jotm.carol.jar           =jotm_carol-1.5.3.jar
-jotm.iiop.jar            =jotm_iiop_stubs-1.5.3.jar
-jotm.jar                 =jotm-1.5.3-patched.jar
+carol.jar                =carol-2.0.5.jar
+jotm.jrmp.jar            =jotm_jrmp_stubs-2.0.10.jar
+jotm.jar                 =jotm-2.0.10.jar
+howl.jar                 =howl-logger-0.1.11.jar
 l2fprod-common-sheet.jar =l2fprod-common-sheet.jar
 log4j.jar                =log4j-1.2.15.jar
 lucene.jar               =lucene-2.0.0.jar

Modified: trunk/build.sh
===================================================================
--- trunk/build.sh	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/build.sh	2008-03-18 09:32:14 UTC (rev 691)
@@ -116,7 +116,7 @@
 fi
 
 # Call Ant
-${JAVABIN} ${ARCH} -Xms64m -Xmx192m -Dant.home=${ANT_HOME} -DJAVAC=${JAVAC} \
+${JAVABIN} ${ARCH} -Xms64m -Xmx256m -Dant.home=${ANT_HOME} -DJAVAC=${JAVAC} \
            -Darch.bits=${ARCH} \
            -Ddir.base=${BASEDIR} \
            -classpath "${CLASSPATH}" org.apache.tools.ant.Main \

Modified: trunk/build.xml
===================================================================
--- trunk/build.xml	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/build.xml	2008-03-18 09:32:14 UTC (rev 691)
@@ -494,7 +494,7 @@
           lib/${date-utils.jar}, lib/${commons-logging.jar}, lib/beepcore-0.9.08.jar, lib/log4j-1.2.15.jar, lib/${lucene.jar}, lib/mail-1.3.jar,
           lib/${jetty.jar}, lib/${jetty.plus.jar}, lib/${jasper.compiler.jar}, lib/${servlet.jar}, lib/jargs-0.2.jar,
           lib/castor-0.9.3.9-xml.jar, lib/trove-1.0.2.jar, lib/${jrdf.jar}, lib/${saaj.jar},
-          lib/${jakarta-oro.jar}, lib/jta-spec1_0_1.jar, lib/${jotm.jar}, lib/${jotm.carol.jar}, lib/${jotm.iiop.jar},
+          lib/${jakarta-oro.jar}, lib/jta-spec1_0_1.jar, lib/${jotm.jar}, lib/${jotm.jrmp.jar}, lib/${carol.jar}, lib/${howl.jar},
           lib/connector-1_5.jar, lib/${httpclient.jar}, lib/${commons-codec.jar}"/>
       <attribute name="Embedded-Main-Class" value="org.mulgara.server.EmbeddedMulgaraServer"/>
     </manifest>
@@ -550,7 +550,7 @@
         ${lucene.jar}, ${mail.jar}, ${castor.jar}, ${trove.jar},
         ${date-utils.jar}, ${commons-logging.jar}, ${commons-httpclient.jar},
         ${commons-codec.jar}, ${emory-util.jar},
-        ${jotm.jar}, ${jotm.carol.jar}, ${jotm.iiop.jar}, ${jta.jar},
+        ${jotm.jar}, ${jotm.jrmp.jar}, ${carol.jar}, ${howl.jar}, ${jta.jar},
         ${jsr.173.api.jar}, ${jsr.173.ri.jar}, ${jca.jar}, ${saaj.jar},
         ${axis.jar}, ${commons-discovery.jar}, ${jaxrpc.jar}, ${wsdl4j.jar},
         ${jena.jar}, ${antlr.jar}, ${jakarta-oro.jar}, {jid3.jar}"/>
@@ -1750,8 +1750,9 @@
       <zipfileset src="${lib.dir}/${commons-codec.jar}" excludes="META-INF/**"/>
       <zipfileset src="${lib.dir}/${emory-util.jar}" excludes="META-INF/**"/>
       <zipfileset src="${lib.dir}/${jotm.jar}" excludes="META-INF/**"/>
-      <zipfileset src="${lib.dir}/${jotm.carol.jar}" excludes="META-INF/**"/>
-      <zipfileset src="${lib.dir}/${jotm.iiop.jar}" excludes="META-INF/**"/>
+      <zipfileset src="${lib.dir}/${jotm.jrmp.jar}" excludes="META-INF/**"/>
+      <zipfileset src="${lib.dir}/${carol.jar}" excludes="META-INF/**"/>
+      <zipfileset src="${lib.dir}/${howl.jar}" excludes="META-INF/**"/>
       <zipfileset src="${lib.dir}/${jta.jar}" excludes="META-INF/**"/>
       <zipfileset src="${lib.dir}/${jsr.173.api.jar}" excludes="META-INF/**"/>
       <zipfileset src="${lib.dir}/${jsr.173.ri.jar}" excludes="META-INF/**"/>

Modified: trunk/common.xml
===================================================================
--- trunk/common.xml	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/common.xml	2008-03-18 09:32:14 UTC (rev 691)
@@ -152,7 +152,7 @@
                        jotm*.jar, commons-logging-*.jar, Quick4rt.jar,
                        commons-discovery-*.jar, commons-httpclient-*.jar,
                        commons-codec-*.jar, connector-*.jar, jaxrpc-*.jar,
-                       ${date-utils.jar}"
+                       carol*.jar, howl-logger*.jar, ${date-utils.jar}"
              excludes="jboss-j2ee.jar"/>
   </path>
 
@@ -280,12 +280,76 @@
       <batchtest todir="${junit.results.tmpdir}">
         <fileset dir="${dir}/java" includes="**/*Test.java"
                  excludes="**/*AbstractTest.java, **/*LoadTest.java,
-                           **/*StressTest.java"/>
+                           **/*StressTest.java, **/*StandaloneTest.java"/>
       </batchtest>
     </junit>
   </target>
 
   <!-- =================================================================== -->
+  <!-- Run JUnit regression tests requiring a standalone instance          -->
+  <!--                                                                     -->
+  <!-- Required parameters: $classpath.id, $dir, $jar                      -->
+  <!-- =================================================================== -->
+  <target name="standalone-test"
+          depends="uri, dtd-jar, host.name, dist"
+          description="run regression tests requiring a standalone instance">
+
+    <copy file="${conf.dir}/log4j-template.xml"
+          tofile="${basedir}/log4j-conf.xml"/>
+
+    <mkdir dir="${junit.results.tmpdir}"/>
+
+    <!-- stop and start the server -->
+    <ant target="stop"/>
+    <ant target="start"/>
+
+    <junit fork="yes" haltonfailure="no" printsummary="on"
+           jvm="${java.home}/bin/java" dir="${basedir}">
+
+      <jvmarg value="${arch.bits}"/>
+      <jvmarg value="-ea"/>
+      <jvmarg value="${jvm.args}"/>
+
+      <sysproperty key="java.io.tmpdir" value="${tmp.dir}"/>
+      <sysproperty key="test.dir" value="${test.dir}"/>
+      <sysproperty key="admin" value=""/>
+      <sysproperty key="itql.command.log" value="${tmp.dir}/itql.log"/>
+      <sysproperty key="org.mulgara.xml.ResourceDocumentBuilderFactory"
+                   value="org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"/>
+      <sysproperty key="java.naming.factory.initial"
+                   value="com.sun.jndi.rmi.registry.RegistryContextFactory"/>
+      <sysproperty key="java.naming.provider.url" value="rmi://localhost"/>
+      <sysproperty key="java.rmi.server.codebase"
+                   value="${bin.uri}/${rmi.base.jar}"/>
+      <sysproperty key="java.security.manager" value=""/>
+      <sysproperty key="java.security.policy"
+                   value="${basedir}/conf/mulgara-test.policy"/>
+      <sysproperty key="host.name" value="${host.name}"/>
+
+      <!-- The cvs.root property allows tests to find CVS'ed test data -->
+      <sysproperty key="cvs.root" value="${basedir}"/>
+      <sysproperty key="mulgara.jar" value="${mulgara.jar}"/>
+      <sysproperty key="log4j.configuration" value="${baseuri}/log4j-conf.xml"/>
+      <sysproperty key="basedir" value="${basedir}"/>
+      <sysproperty key="org.mulgara.test" value="${org.mulgara.test}"/>
+
+      <classpath>
+        <fileset dir="${dist.dir}" includes="${mulgara.jar}"/>
+      </classpath>
+
+      <formatter type="xml"/>
+
+      <batchtest todir="${junit.results.tmpdir}">
+        <fileset dir="${dir}/java" includes="**/*StandaloneTest.java"/>
+      </batchtest>
+    </junit>
+
+    <!-- stop the server -->
+    <ant target="stop"/>
+
+  </target>
+
+  <!-- =================================================================== -->
   <!-- Run JUnit load tests on a JAR component                             -->
   <!--                                                                     -->
   <!-- Required parameters: $classpath.id, $dir, $jar                      -->

Copied: trunk/docs/site-src/design/JTAInterface.txt (from rev 690, branches/mgr-73/docs/site-src/design/JTAInterface.txt)
===================================================================
--- trunk/docs/site-src/design/JTAInterface.txt	                        (rev 0)
+++ trunk/docs/site-src/design/JTAInterface.txt	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,271 @@
+The Mulgara JTA Interface
+
+The Existing Interface
+
+Mulgara originally provided two transactional interfaces, implicit and explicit.
+This mediated via the Session object, and the method used to select between them
+is called setAutoCommit().  When auto-commit is true, all operations on the
+Session use the implicit interface.  Setting auto-commit false initiates an
+explicit (or user-demarcated) transaction.  Irrespective of any commit/rollback,
+once auto-commit is false all transactions are user-demarcated until it is set
+to true again.  While auto-commit is true every operation on the Session will
+initiate a new transaction which will be finalised (either commit or rollback)
+before the operation returns.
+
+All transactions initiated via the explicit interface are read-write, whereas
+only those operations that perform updates initiate implicit read-write
+transactions.  Naturally both interfaces use the same underlying transactional
+mechanisms within Mulgara, including a single shared write-lock that only
+permits a single read-write transaction to proceed at a time.
+
+Explicit transactions are controlled via three methods on the Session interface.
+ - Session::setAutoCommit() allows the developer to select which transactional
+   interface to use, and to initiate a transaction (this does conflate two
+   concepts, but todate that has not proven to be a problem).
+ - Session::commit() commits the current transaction an initiates a new
+   transaction without dropping the write-lock.
+ - Session::rollback() rollbacks the current transaction an initiates a new
+   transaction without dropping the write-lock.
+There are two other ways for a user-demarcated transaction to be terminated.
+ - If an exception is thrown from an operation on the session, or on a query
+   result associated with the transaction, the transaction is rolled back and
+   the write-lock is dropped.
+ - If the transaction timeout expires then the transaction is rolled back and
+   the write-lock is dropped.
+In both cases the session remains in explicit transaction control however as
+all operations start by attempting to activate the transaction, any attempt to
+perform any non-transaction control operation on the session results in an
+exception.
+
+To ensure full transaction isolation/serialisability each answer returned from a
+query holds a reference to the transaction/phase it was performed against.  This
+means that the results of queries performed within an implicit transaction
+remain valid until such a time as either the result or the originating session
+itself is closed.  However due to this reliance on the originating transaction,
+query results obtained from an explicit transaction do not out live it, and are
+invalidated as soon as the transaction is terminated.
+
+This approach greatly simplifies the normal case of an application that is
+performing an atomic insert, delete, or query.  In this case the application
+developer can ignore transactions altogether and the implict transaction control
+will 'do the right thing'.  For more complex applications, the explicit
+transation controls available on session are sufficient for almost all uses of
+mulgara as a standalone store.
+
+Motivation for a new interface
+
+There are however two gaps in the original transaction interface.
+
+The first is that it is impossible to perform a series of transactionally
+consistant queries except by grouping them into a single call to
+Session::query(List); or by first initiating an explicit transaction in order to
+obtain the write-lock, preventing any potentially inconsistent updates.
+Naturally the former is not always possible, and the latter is undesirable,
+especially as mulgara is a multiversion datastore and it therefore should be
+possible to obtain a handle to a given version (phase) and run multiple queries
+against it.
+
+The second is that the interface provides no access to the two-phase commit
+protocol used by mulgara internally.  This isn't a problem when using mulgara as
+a standalone server, however it does make it impossible to incorporate mulgara
+in a distributed transaction with other stores or databases safely.
+
+The Java standard for providing access to a two-phase commit protocol is the
+"Java Transaction API" (JTA).  A java transaction (and slight simplification) of
+the Object Management Group (OMG)'s "Transaction Service Specification" (OTS), which
+is the transaction management specification for CORBA.  The OTS is itself an
+object-oriented mapping of the "DTP/XA Specification" (XA-spec)from X/Open (now The Open
+Group).  The relevant versions of the various specifications are:
+  - JTA : Java Transaction API (JTA), Version 1.1, 2002
+  - OTS : Transaction Service Specification, Version 1.2, 2001
+  - XA-Spec : Distributed Transaction Processing: The XA Specification,
+    XO/CAE/91/300, 1991
+The XA-Spec relies for context on the "DTP/Reference Model" (XA-model), 1991;
+although the mulgara JTA implementation was guided with reference to the more
+recent version 3, 1996.
+  - XA-model : Distribute Transaction Processing: Reference Model Version 3,
+    G504, 1996
+
+While the primary requirement for JTA support is the need to provide a two-phase
+commit interface, the opportunity was also taken to provide explicit control of
+read-only transactions which solves the problem of supporting multiple
+consistent reads as discussed above.
+
+Limitations of the JTA interface
+
+The write-lock is not a consequence of the existing interface, but rather a
+property of the underlying mulgara XA1 store implementation.  As a result the
+use of JTA does not affect the serialisation of write transactions.  It remains
+the case that any attempt to initiate a write-transaction will block until such
+a time as the write-lock becomes available.  The current JTA implementation
+within mulgara does not provide any timeout on this wait, so application
+developers should be aware that calls to start a mulgara transaction may block
+for an arbitary time.  
+
+On restart mulgara automatically and releases (garbage collects) any phases
+(versions) other than the most recently committed.  This startup protocol is
+done without any journal reruns resulting in an instantaneous restart.  On the
+other hand this protocol does mean that mulgara discards any prepared
+transactions on restart, effectively performing a heuristic rollback.  As
+mulgara makes no distinction between prepared and unprepared transactions on
+restart, mulgara does not persist the prepared status of a transaction.  This
+means that the mulgara JTA implementation cannot participate in the JTA recovery
+protocol.  As a result on restart users should consider all transactions to be
+Heuristically Completed (state S5 in XA-spec 6.3), with status XA_HEURRB in the
+case of a restart prior to a call to commit, and status XA_HEURHAZ on a restart
+during a commit call.  It is apropos here to recall that XA_HEURMIX is only
+possible if an update operation is performed on an external datastore via a
+resolver (at the moment only lucene supports this); atomicity is guaranteed
+with respect to the mulgara rdf-store itself.
+
+The JTA interface is a Java interface that is expected to operate transparently
+over a network.  As with Mulgara's session interface, the JTA interface uses RMI
+to provide network transparency.  This should not be a problem as the RMI based
+JTA implementation can only be obtained via the RMI based session
+implementation, however any interruption to RMI operation will affect 
+transaction control.
+
+Accessing the JTA interface
+
+The fundamental requirement for supporting JTA is providing a way of obtaining
+an XAResource.  JTA requires that each XAResource be associated with a "Resource
+Manager Instance".  When mapping this to Mulgara, and taking into consideration
+the code in the JTA-spec (3.4.9), the Session object was identified as best
+fitting JTA's use of the term RM instance.  The JTA interface therefore adds two
+new methods to Session:
+  Session::getXAResource()
+  Session::getReadOnlyXAResource()
+Both return an XAResource object, associated with the session.  All transactions
+initiated via a call to XAResource::start() will be read-write or read-only
+depending on if the XAResource was obtained via getXAResource or
+getReadOnlyXAResource respectively.
+
+The preexisting interface manages the transaction state transitions internally,
+as compared to the JTA interface which exposes these transitions to the external
+control of an external transaction manager.  Consequently within mulgara
+transactions created via the JTA interface are referred to as "external
+transactions", those by the older implicit and explicit interface as "internal
+transactions".  While JTA 3.4.7 encourages supporting both Local (internal) and
+Global (external) transactions through the same "connection", supporting this
+would drastically increase the number of operation combinations that would need
+to be tested to ensure confidence in the transaction logic.  Therefore the
+current mulgara JTA implementation does not support the use of both internal and
+external transactions on the same session.  To select which interface is active
+on a given session the developer should simply use it.  The first use of a given
+interface (internal or external) disables the other.  This ensures full
+backwards compatibility with existing code while simplifying the process of
+testing and building confidence in the new interface.
+
+Using the JTA interface
+
+It is worth mentioning that none of the underlying transactional machinery has
+changed, consequently all Answer objects still reference their originating
+transaction, and all transactions ultimately must map to a specific phase on the
+store.  As a result it is important to keep in mind that once a JTA mediated
+transaction is terminated via the XAResource, the reference to the phase is
+released and all outstanding Answer objects are invalidated.  In this the JTA
+interface works very similarly to the explict internal interface.
+
+JTA also recommends supporting multiple outstanding transactions on a single RM
+instance.  The mulgara JTA implementation provides full support for this,
+however developers attempting to use this functionality *must* be careful to
+observe the restrictions imposed by the JTA standard.  JTA provides support for
+concurrent transactions on a single resource adaptor via calls to
+XAResource::suspend() and XAResource::resume().  These transition the specified
+transaction from Associated to Suspended states (see JTA-spec 3.4.4, although
+XA-spec Ch.6 is more complete).  JTA requires (as does the mulgara
+implementation) that at most one transaction be associated with the
+transactional resource at any given time.
+
+The following example of concurrent use of JTA transactions on a single session
+is excerpted from org.mulgara.resolver.JotmTransactionStandaloneUnitTest.java
+
+{
+  txManager.begin();
+
+  Session session = sessionFactory.newSession();
+  XAResource roResource = session.getReadOnlyXAResource();
+  XAResource rwResource = session.getXAResource();
+
+  /*
+   * Note: JTA requires that you initiate the global transaction (via mgr::begin()
+   * above), before you enlist a resource.
+   * The call to enlistResource for an XAResource for the first time in a given
+   * global transaction will cause the Transaction Manager to call xa::start() on
+   * the XAResource, initiating a mulgara transaction.
+   * As we are using an XAResource obtained via xa::getXAResource(), this
+   * transaction will be a read-write transaction - note this does mean that
+   * this call could block waiting on the write-lock availability.
+   */
+  txManager.getTransaction().enlistResource(rwResource);
+  session.createModel(...);
+  Transaction tx1 = txManager.suspend();
+  /*
+   * Note: Without the call to mgr::suspend, subsequent calls to enlistResource will
+   * detect that there is an existing transaction and result in a call to
+   * xa::start(TM_JOIN), which is either a no-op (if a read-only 'joins' a
+   * read-write transaction) or an exception (if a read-write attempts to 'join'
+   * a read-only transaction).
+   * With the call to mgr::suspend() the transaction is temporarally
+   * disassociated from this thread, and from this session.  A Consequent call to
+   * mgr::begin() will result in the transaction manager initiating a new
+   * transaction, and an ensuing call to xa::enlistResource a new mulgara
+   * transaction.
+   * It is important to realise that all operations on a session object are
+   * evaluated with respect to the currently associated transaction.  If all
+   * transactions are suspended or completed then operations will result in an
+   * exception.
+   */
+
+  txManager.begin();
+  txManager.getTransaction().enlistResource(roResource);
+  Answer answer = session.query(new Query(...));
+  Transaction tx2 = txManager.suspend();
+
+  /*
+   * Answers carry their own association with the mulgara transaction, so the
+   * transaction does not need to be reassociated with the originating session
+   * to use an Answer object.  This does run counter to assumptions of the JTA
+   * specification, however as the JTA specification assumes a traditional
+   * relational database, many of the capabilities of multiversion datastores
+   * (such as mulgara) flexibility is required in adapting multiversion
+   * transactions to JTA.
+   */
+  answer.beforeFirst();
+  while (answer.next()) {
+    ...
+  }
+
+  txManager.resume(tx1);
+  session.insert(...);
+  tx1 = txManager.suspend();
+  answer.close();
+  txManager.resume(tx1);
+  txManager.commit();
+
+  /*
+   * Note the suport for user-demarcated concurrent read-only transactions.
+   */
+  txManager.begin();
+  txManager.getTransaction().enlistResource(roResource);
+  Answer answer2 = session.query(new Query(...));
+  Transaction tx3 = txManager.suspend();
+
+  // use answer2.
+  answer2.close();
+
+  /*
+   * Note the call to tx3.commit() is performed on a suspended transaction.  As
+   * tx3 is a handle to the transaction it does not need to be reassociated with
+   * the current thread as is required if it is going to be committed via the
+   * transaction manager.  This flexibility is mandated by JTA.
+   */
+  txManager.begin();
+  txManager.getTransaction().enlistResource(rwResource);
+  session.removeModel(...);
+  txManager.commit();
+  txManager.resume(tx2);
+  txManager.commit();
+  tx3.commit();
+  session.close();
+}

Modified: trunk/jxdata/iTQL/data_types/queryResult34.txt
===================================================================
--- trunk/jxdata/iTQL/data_types/queryResult34.txt	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/jxdata/iTQL/data_types/queryResult34.txt	2008-03-18 09:32:14 UTC (rev 691)
@@ -1,4 +1,4 @@
-ItqlInterpreter error - Could not commit insert
+ItqlInterpreter error - Could not commit modify
 Caused by: (QueryException) org.mulgara.query.MulgaraTransactionException: Transaction rollback triggered
 Caused by: (QueryException) org.mulgara.resolver.spi.ResolverException: Unable to read input statements
 Caused by: (QueryException) org.mulgara.query.TuplesException: Failed to localize node

Modified: trunk/jxdata/iTQL/standard_queries/queryResult17.txt
===================================================================
--- trunk/jxdata/iTQL/standard_queries/queryResult17.txt	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/jxdata/iTQL/standard_queries/queryResult17.txt	2008-03-18 09:32:14 UTC (rev 691)
@@ -1,3 +1,3 @@
-ItqlInterpreter error - Could not commit insert
+ItqlInterpreter error - Could not commit modify
 Caused by: (QueryException) org.mulgara.query.MulgaraTransactionException: Transaction rollback triggered
 Caused by: (QueryException) rmi://localhost/server1#nomodelexistswiththisname is not a Model

Copied: trunk/lib/carol-2.0.5.jar (from rev 690, branches/mgr-73/lib/carol-2.0.5.jar)
===================================================================
(Binary files differ)

Copied: trunk/lib/howl-logger-0.1.11.jar (from rev 690, branches/mgr-73/lib/howl-logger-0.1.11.jar)
===================================================================
(Binary files differ)

Deleted: trunk/lib/jotm-1.5.3-patched.jar
===================================================================
(Binary files differ)

Copied: trunk/lib/jotm-2.0.10.jar (from rev 690, branches/mgr-73/lib/jotm-2.0.10.jar)
===================================================================
(Binary files differ)

Deleted: trunk/lib/jotm_carol-1.5.3.jar
===================================================================
(Binary files differ)

Deleted: trunk/lib/jotm_iiop_stubs-1.5.3.jar
===================================================================
(Binary files differ)

Copied: trunk/lib/jotm_jrmp_stubs-2.0.10.jar (from rev 690, branches/mgr-73/lib/jotm_jrmp_stubs-2.0.10.jar)
===================================================================
(Binary files differ)

Modified: trunk/src/jar/itql/java/org/mulgara/itql/TqlInterpreter.java
===================================================================
--- trunk/src/jar/itql/java/org/mulgara/itql/TqlInterpreter.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/itql/java/org/mulgara/itql/TqlInterpreter.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -814,7 +814,7 @@
 
     // build the variable list: collection of Variable, ConstantValue, Count, Subquery
     if (logger.isDebugEnabled()) logger.debug("Building query variable list from " + variables);
-    List<Object> variableList = this.buildVariableList(variables);
+    List<SelectElement> variableList = this.buildVariableList(variables);
     if (logger.isDebugEnabled()) logger.debug("Built variable list " + variableList);
   
     // get the model expression from the parser
@@ -999,11 +999,11 @@
    *      into a list of {@link org.mulgara.query.Variable}s
    */
   @SuppressWarnings("unchecked")
-  List<Object> buildVariableList(LinkedList<PElement> rawVariableList) throws
+  List<SelectElement> buildVariableList(LinkedList<PElement> rawVariableList) throws
       QueryException, URISyntaxException {
   
     // Empty variable list.
-    if (rawVariableList == null) return (List<Object>)Collections.EMPTY_LIST;
+    if (rawVariableList == null) return Collections.emptyList();
   
     // validate rawVariableList parameter
     if (rawVariableList.size() == 0) throw new IllegalArgumentException("Empty \"rawVariableList\" parameter");
@@ -1019,7 +1019,7 @@
     for (PElement element: rawVariableList) element.apply((Switch)variableBuilder);
   
     // Get the variable list
-    List<Object> variableList = variableBuilder.getVariableList();
+    List<SelectElement> variableList = variableBuilder.getVariableList();
   
     // make sure that we return a list with something in it
     if (variableList.size() == 0) {

Modified: trunk/src/jar/itql/java/org/mulgara/itql/VariableBuilder.java
===================================================================
--- trunk/src/jar/itql/java/org/mulgara/itql/VariableBuilder.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/itql/java/org/mulgara/itql/VariableBuilder.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -78,7 +78,7 @@
    * The list of variables - these are mixed object types:
    * ConstantValue, Variable, Count, SubQuery
    */
-  private List<Object> variableList;
+  private List<SelectElement> variableList;
 
   /** The TQL interpreter */
   private SableCCInterpreter interpreter;
@@ -90,7 +90,7 @@
    * @param newInterpreter the interpreter to use.
    */
   public VariableBuilder(SableCCInterpreter newInterpreter, VariableFactory newVariableFactory) {
-    variableList = new ArrayList<Object>();
+    variableList = new ArrayList<SelectElement>();
     interpreter = newInterpreter;
     variableFactory = newVariableFactory;
   }
@@ -182,9 +182,9 @@
    * @throws URISyntaxException if the variable contains a resource whose
    *   text violates <a href="http://www.isi.edu/in-notes/rfc2396.txt">RFC?2396</a>
    */
-  public List<Object> getVariableList() throws QueryException, URISyntaxException {
+  public List<SelectElement> getVariableList() throws QueryException, URISyntaxException {
     try {
-      List<Object> tmpVariableList = new ArrayList<Object>(variableList);
+      List<SelectElement> tmpVariableList = new ArrayList<SelectElement>(variableList);
 
       if (uriException != null) throw uriException;
       else if (queryException != null) throw queryException;

Modified: trunk/src/jar/krule/java/org/mulgara/krule/QueryStruct.java
===================================================================
--- trunk/src/jar/krule/java/org/mulgara/krule/QueryStruct.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/krule/java/org/mulgara/krule/QueryStruct.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -48,6 +48,7 @@
 import org.mulgara.query.ModelUnion;
 import org.mulgara.query.Order;
 import org.mulgara.query.Query;
+import org.mulgara.query.SelectElement;
 import org.mulgara.query.UnconstrainedAnswer;
 import org.mulgara.query.Variable;
 import org.mulgara.query.VariableFactory;
@@ -76,7 +77,7 @@
   private ConstraintElement[] select = new ConstraintElement[3];
 
   /** List of elements which are variables, or ConstantValues. */
-  private List<Object> variables;
+  private List<SelectElement> variables;
 
   /** The model expresison for the query. */
   private ModelExpression models;
@@ -112,7 +113,7 @@
     VariableFactory variableFactory = new VariableFactoryImpl();
 
     // set up a list of variables
-    variables = new ArrayList<Object>();
+    variables = new ArrayList<SelectElement>();
 
     // convert the parameters to usable objects
     for (int i = 0; i < 3; i++) {
@@ -129,7 +130,7 @@
 
         // get the variable
         select[i] = (Variable)varReferences.get(element);
-        variables.add(select[i]);
+        variables.add((Variable)select[i]);
 
       } else if (types[i].equals(KruleLoader.LITERAL)) {
         

Modified: trunk/src/jar/query/java/org/mulgara/query/AggregateFunction.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/AggregateFunction.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/query/java/org/mulgara/query/AggregateFunction.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -49,7 +49,7 @@
  *
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
-public abstract class AggregateFunction implements Serializable {
+public abstract class AggregateFunction implements SelectElement, Serializable {
 
   /**
    * Allow newer compiled version of the stub to operate when changes

Modified: trunk/src/jar/query/java/org/mulgara/query/ConstantValue.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/ConstantValue.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/query/java/org/mulgara/query/ConstantValue.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -48,7 +48,7 @@
  *
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
-public class ConstantValue implements Serializable {
+public class ConstantValue implements SelectElement, Serializable {
 
   /**
    * Allow newer compiled version of the stub to operate when changes

Modified: trunk/src/jar/query/java/org/mulgara/query/Query.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/Query.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/query/java/org/mulgara/query/Query.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
  * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
  * Plugged In Software Pty Ltd. All Rights Reserved.
  *
- * Contributor(s): N/A.
+ * Contributor(s):
+ *   Move to java-generics copyright Netymon Pty Ltd
  *
  * [NOTE: The text of this Exhibit A may differ slightly from the text
  * of the notices in the Source Code files of the Original Code. You
@@ -72,13 +73,13 @@
    * that there is no <code>select</code> clause and that no projection will be
    * performed.
    */
-  private List<Object> variableList;
+  private List<SelectElement> variableList;
 
   /**
    * Mutable version of the variable list. This isn't exposed via {@link
    * #getVariableList} the way {@link #variableList} is.
    */
-  private List<Object> mutableVariableList;
+  private List<SelectElement> mutableVariableList;
 
   /** The model expression. It corresponds to the <code>from</code> clause. */
   private ModelExpression modelExpression;
@@ -142,7 +143,7 @@
    *     <var>constraintExpression</var>, <var>orderList<var> or
    *     <var>answer</var> are <code>null</code>
    */
-  public Query(List<Object> variableList, ModelExpression modelExpression,
+  public Query(List<? extends SelectElement> variableList, ModelExpression modelExpression,
       ConstraintExpression constraintExpression,
       ConstraintHaving havingExpression, List<Order> orderList, Integer limit,
       int offset, Answer answer) {
@@ -177,7 +178,7 @@
     }
 
     // Initialize fields
-    this.mutableVariableList = (variableList == null) ? null : new ArrayList<Object>(variableList);
+    this.mutableVariableList = (variableList == null) ? null : new ArrayList<SelectElement>(variableList);
     this.variableList = (variableList == null) ? null : Collections.unmodifiableList(mutableVariableList);
     this.modelExpression = modelExpression;
     this.constraintExpression = constraintExpression;
@@ -233,10 +234,8 @@
       cloned.mutableVariableList = null;
       cloned.variableList = null;
     } else {
-      cloned.variableList = new ArrayList<Object>();
-      Iterator<Object> i = variableList.iterator();
-      while (i.hasNext()) {
-        Object o = i.next();
+      cloned.variableList = new ArrayList<SelectElement>();
+      for (SelectElement o : variableList) {
         if (o instanceof Subquery) {
           Subquery s = (Subquery)o;
           cloned.variableList.add(new Subquery(s.getVariable(), (Query)s.getQuery().clone()));
@@ -270,7 +269,7 @@
    * @return a {@link List} containing one or more {@link Variable}s, {@link ContantValue}s,
    * {@link Count}s or {@link Subquery}
    */
-  public List<Object> getVariableList() {
+  public List<SelectElement> getVariableList() {
     return variableList;
   }
 
@@ -407,8 +406,10 @@
     answer = null;
 
     if (mutableVariableList != null) {
-      for (Object v: mutableVariableList) {
-        if (v instanceof AggregateFunction) ((AggregateFunction)v).getQuery().close();
+      for (SelectElement v: mutableVariableList) {
+        if (v instanceof AggregateFunction) {
+          ((AggregateFunction)v).getQuery().close();
+        }
       }
     }
   }

Modified: trunk/src/jar/query/java/org/mulgara/query/QueryUnitTest.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/QueryUnitTest.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/query/java/org/mulgara/query/QueryUnitTest.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -45,18 +45,8 @@
  * Purpose: Test case for {@link Query}.
  *
  * @created 2001-10-23
- *
  * @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a>
- *
- * @version $Revision: 1.8 $
- *
- * @modified $Date: 2005/01/05 04:58:20 $ by $Author: newmana $
- *
- * @maintenanceAuthor $Author: newmana $
- *
- * @copyright &copy;2001-2004
- *   <a href="http://www.pisoftware.com/">Plugged In Software Pty Ltd</a>
- *
+ * @copyright &copy;2001-2004 <a href="http://www.pisoftware.com/">Plugged In Software Pty Ltd</a>
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
 public class QueryUnitTest extends TestCase {
@@ -84,7 +74,6 @@
    * @return The test suite
    */
   public static Test suite() {
-
     TestSuite suite = new TestSuite();
 
     suite.addTest(new QueryUnitTest("test1Equals"));
@@ -100,7 +89,6 @@
    * @param args the command line arguments
    */
   public static void main(String[] args) {
-
     junit.textui.TestRunner.run(suite());
   }
 
@@ -111,9 +99,8 @@
    */
   @SuppressWarnings("unchecked")
   public void setUp() throws Exception {
-
     query = new Query(
-        Arrays.asList(new Object[] {new Variable("x")}), // variable list
+        Arrays.asList(new SelectElement[] { new Variable("x") }), // variable list
         new ModelResource(new URI("x:m")),      // model expression
         new ConstraintImpl(new Variable("x"),   // constraint expression
         new URIReferenceImpl(new URI("x:p")),
@@ -135,7 +122,6 @@
    * @throws Exception if query fails when it should have succeeded
    */
   public void testClone() throws Exception {
-
     Query copied = (Query) query.clone();
     assertTrue(copied != query);
     assertEquals(copied, query);
@@ -147,9 +133,7 @@
    * @throws Exception if query fails when it should have succeeded
    */
   public void test1Equals() throws Exception {
-
     Query query2 = query;
-
     assertTrue(query.equals(query2));
   }
 
@@ -160,16 +144,15 @@
    */
   @SuppressWarnings("unchecked")
   public void test2Equals() throws Exception {
-
     // Compose test instances
     Query query2 = new Query(
-        Arrays.asList(new Object[] {new Variable("x")}),                    // variable list
+        Arrays.asList(new SelectElement[] { new Variable("x") }), // variable list
         new ModelResource(new URI("x:m")),      // model expression
         new ConstraintImpl(new Variable("x"),   // constraint expression
         new URIReferenceImpl(new URI("x:p")),
         new LiteralImpl("o")),
         null,                                  // no having
-        (List<Order>)Collections.EMPTY_LIST,                // no ordering
+        (List<Order>)Collections.EMPTY_LIST,   // no ordering
         null,                                  // no limit
         0,                                     // zero offset
         new UnconstrainedAnswer());
@@ -186,7 +169,6 @@
    * @throws Exception if query fails when it should have succeeded
    */
   public void test3Equals() throws Exception {
-
     assertTrue(!query.equals(null));
   }
 }

Copied: trunk/src/jar/query/java/org/mulgara/query/SelectElement.java (from rev 690, branches/mgr-73/src/jar/query/java/org/mulgara/query/SelectElement.java)
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/SelectElement.java	                        (rev 0)
+++ trunk/src/jar/query/java/org/mulgara/query/SelectElement.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2008 The Topaz Foundation 
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+package org.mulgara.query;
+
+import java.io.Serializable;
+
+/**
+ * Marker interface to identify elements legally permitted within the
+ * select-clause of an itql query.
+ *
+ * @created 2008-01-14
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ * @copyright &copy;2008 <a href="http://www.topazproject.org/">The Topaz Foundation</a>
+ * @licence Apache License v2.0
+ */
+public interface SelectElement extends Serializable, Cloneable { }

Modified: trunk/src/jar/query/java/org/mulgara/query/Variable.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/Variable.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/query/java/org/mulgara/query/Variable.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -45,7 +45,7 @@
  *
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
-public class Variable implements ConstraintElement {
+public class Variable implements SelectElement, ConstraintElement {
 
   /**
    * Allow newer compiled version of the stub to operate when changes

Copied: trunk/src/jar/query/java/org/mulgara/server/ResourceManagerInstanceAdaptor.java (from rev 690, branches/mgr-73/src/jar/query/java/org/mulgara/server/ResourceManagerInstanceAdaptor.java)
===================================================================
--- trunk/src/jar/query/java/org/mulgara/server/ResourceManagerInstanceAdaptor.java	                        (rev 0)
+++ trunk/src/jar/query/java/org/mulgara/server/ResourceManagerInstanceAdaptor.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2008 The Topaz Foundation 
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+package org.mulgara.server;
+
+
+// Java 2 Standard Packages
+import java.net.*;
+import java.util.*;
+import java.io.*;
+import javax.transaction.xa.XAResource;
+
+/**
+ * Interface to identify a Resource Manager through an XAResource.
+ *
+ * @created 2008-01-14
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ * @copyright &copy;2008 <a href="http://www.topazproject.org/">The Topaz Foundation</a>
+ * @licence Apache License v2.0
+ */
+public interface ResourceManagerInstanceAdaptor {
+  /**
+   * Returns an object that globally identifies a ResourceManagerInstance.
+   * Returned object must override equals/hashcode to compare equal by value.
+   */
+  public Serializable getRMId();
+}

Modified: trunk/src/jar/query/java/org/mulgara/server/Session.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/server/Session.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/query/java/org/mulgara/server/Session.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
  * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
  * Plugged In Software Pty Ltd. All Rights Reserved.
  *
- * Contributor(s): N/A.
+ * Contributor(s):
+ *    XAResource addition copyright 2008 The Topaz Foundation
  *
  * [NOTE: The text of this Exhibit A may differ slightly from the text
  * of the notices in the Source Code files of the Original Code. You
@@ -32,6 +33,7 @@
 import java.net.*;
 import java.util.*;
 import java.io.*;
+import javax.transaction.xa.XAResource;
 
 // Locally written packages
 import org.jrdf.graph.Triple;
@@ -79,7 +81,7 @@
    * @param statements The Set of statements to insert into the model.
    * @throws QueryException if the insert cannot be completed.
    */
-  public void insert(URI modelURI, Set<Triple> statements) throws QueryException;
+  public void insert(URI modelURI, Set<? extends Triple> statements) throws QueryException;
 
   /**
    * Insert statements from the results of a query into another model.
@@ -97,7 +99,7 @@
    * @param statements The Set of statements to delete from the model.
    * @throws QueryException if the deletion cannot be completed.
    */
-  public void delete(URI modelURI, Set<Triple> statements) throws QueryException;
+  public void delete(URI modelURI, Set<? extends Triple> statements) throws QueryException;
 
   /**
    * Delete statements from a model using the results of query.
@@ -304,4 +306,30 @@
    */
   public void login(URI securityDomain, String username, char[] password);
 
+  /**
+   * Obtain an XAResource for this Session.
+   *
+   * Use of this method is incompatible with any use of implicit or internally
+   * mediated transactions with this Session.
+   * Transactions initiated from the XAResource returned by the read-only
+   * version of this method will be read-only.
+   */
+  public XAResource getXAResource() throws QueryException;
+  public XAResource getReadOnlyXAResource() throws QueryException;
+
+  /**
+   * This class is just a devious way to get static initialization for the
+   * {@link Session} interface.
+   */
+  abstract class ConstantFactory {
+
+    static URI getMulgaraModelURI() {
+      try {
+        return new URI(Mulgara.NAMESPACE + "Model");
+      }
+       catch (URISyntaxException e) {
+        throw new Error("Bad hardcoded URI");
+      }
+    }
+  }
 }

Modified: trunk/src/jar/resolver/build.xml
===================================================================
--- trunk/src/jar/resolver/build.xml	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/build.xml	2008-03-18 09:32:14 UTC (rev 691)
@@ -32,6 +32,7 @@
     <fileset file="${resolver-jar.dist.dir}/${resolver-jar.jar}"/>
     <fileset file="${resolver-url.dist.dir}/${resolver-url.jar}"/>
     <fileset file="${content-rdfxml.dist.dir}/${content-rdfxml.jar}"/>
+    <fileset file="${driver.dist.dir}/${driver.jar}"/>
   </path>
 
   <path id="resolver-test-classpath">
@@ -81,7 +82,7 @@
           depends="-resolver-prepare, resolver-spi-jar, rules-jar,
                    resolver-http-jar, resolver-file-jar, resolver-url-jar,
                    resolver-jar-jar, content-rdfxml-jar, content-rdfxml-jar,
-                   client-jrdf-jar, jrdf-jar, resolver-xsd-jar"
+                   client-jrdf-jar, jrdf-jar, resolver-xsd-jar, driver-jar"
           description="Compiles all resolver related files included generated
                        source code">
 
@@ -128,14 +129,21 @@
                    resolver-jar, resolver-memory-jar, resolver-xsd-jar,
                    store-nodepool-xa-jar, store-stringpool-xa-jar, resolver-store-jar,
                    resolver-url-jar, store-stringpool-memory-jar, tuples-hybrid-jar,
-                   util-jar, util-xa-jar">
+                   util-jar, util-xa-jar, driver-jar">
 
+
     <antcall target="component-test">
 
       <param name="classpath.id" value="resolver-test-classpath"/>
       <param name="dir" value="${resolver.src.dir}"/>
       <param name="jar" value="${resolver.jar}"/>
     </antcall>
+
+    <antcall target="standalone-test">
+      <param name="classpath.id" value="resolver-test-classpath"/>
+      <param name="dir" value="${resolver.src.dir}"/>
+      <param name="jar" value="${resolver.jar}"/>
+    </antcall>
   </target>
 
   <target name="resolver-javadoc"

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -63,6 +63,7 @@
 import org.mulgara.query.Order;
 import org.mulgara.query.Query;
 import org.mulgara.query.QueryException;
+import org.mulgara.query.SelectElement;
 import org.mulgara.query.Subquery;
 import org.mulgara.query.TuplesException;
 import org.mulgara.query.UnconstrainedAnswer;
@@ -243,7 +244,7 @@
         Variable predicateVariable = new Variable("predicate");
         Variable objectVariable    = new Variable("object");
 
-        List<Object> selectList = new ArrayList<Object>(3);
+        List<SelectElement> selectList = new ArrayList<SelectElement>(3);
         selectList.add(subjectVariable);
         selectList.add(predicateVariable);
         selectList.add(objectVariable);
@@ -301,7 +302,7 @@
         Variable predicateVariable = new Variable("predicate");
         Variable objectVariable    = new Variable("object");
 
-        List<Object> selectList = new ArrayList<Object>(3);
+        List<SelectElement> selectList = new ArrayList<SelectElement>(3);
         selectList.add(subjectVariable);
         selectList.add(predicateVariable);
         selectList.add(objectVariable);
@@ -361,10 +362,10 @@
         Variable predicateVariable = new Variable("predicate");
         Variable objectVariable    = new Variable("object");
 
-        List<Object> selectList = new ArrayList<Object>(3);
+        List<SelectElement> selectList = new ArrayList<SelectElement>(3);
         selectList.add(subjectVariable);
         selectList.add(new Subquery(new Variable("k0"), new Query(
-          Collections.singletonList((Object)objectVariable),
+          Collections.singletonList(objectVariable),
           new ModelResource(modelURI),                      // FROM
           new ConstraintImpl(subjectVariable,               // WHERE
                          predicateVariable,
@@ -436,10 +437,10 @@
         Variable predicateVariable = new Variable("predicate");
         Variable objectVariable    = new Variable("object");
 
-        List<Object> selectList = new ArrayList<Object>(3);
+        List<SelectElement> selectList = new ArrayList<SelectElement>(3);
         selectList.add(subjectVariable);
         selectList.add(new Subquery(new Variable("k0"), new Query(
-          Collections.singletonList((Object)objectVariable),
+          Collections.singletonList(objectVariable),
           new ModelResource(modelURI),                      // FROM
           new ConstraintImpl(subjectVariable,               // WHERE
                          predicateVariable,
@@ -525,7 +526,7 @@
         Variable predicateVariable = new Variable("predicate");
         Variable objectVariable    = new Variable("object");
 
-        List<Object> selectList = new ArrayList<Object>(3);
+        List<SelectElement> selectList = new ArrayList<SelectElement>(3);
         selectList.add(subjectVariable);
         selectList.add(predicateVariable);
         selectList.add(objectVariable);
@@ -612,7 +613,7 @@
         Variable predicateVariable = new Variable("predicate");
         Variable objectVariable    = new Variable("object");
 
-        List<Object> selectList = new ArrayList<Object>(3);
+        List<SelectElement> selectList = new ArrayList<SelectElement>(3);
         selectList.add(subjectVariable);
         selectList.add(predicateVariable);
         selectList.add(objectVariable);
@@ -688,7 +689,7 @@
           Variable predicateVariable = new Variable("predicate");
           Variable objectVariable    = new Variable("object");
 
-          List<Object> selectList = new ArrayList<Object>(3);
+          List<SelectElement> selectList = new ArrayList<SelectElement>(3);
           selectList.add(subjectVariable);
           selectList.add(predicateVariable);
           selectList.add(objectVariable);
@@ -716,7 +717,7 @@
 
           session1.setAutoCommit(true);
 
-          selectList = new ArrayList<Object>(3);
+          selectList = new ArrayList<SelectElement>(3);
           selectList.add(subjectVariable);
           selectList.add(predicateVariable);
           selectList.add(objectVariable);
@@ -785,7 +786,7 @@
           Variable predicateVariable = new Variable("predicate");
           Variable objectVariable    = new Variable("object");
 
-          List<Object> selectList = new ArrayList<Object>(3);
+          List<SelectElement> selectList = new ArrayList<SelectElement>(3);
           selectList.add(subjectVariable);
           selectList.add(predicateVariable);
           selectList.add(objectVariable);
@@ -814,7 +815,7 @@
           session1.rollback();
           session1.setAutoCommit(true);
 
-          selectList = new ArrayList<Object>(3);
+          selectList = new ArrayList<SelectElement>(3);
           selectList.add(subjectVariable);
           selectList.add(predicateVariable);
           selectList.add(objectVariable);
@@ -869,7 +870,7 @@
           Variable predicateVariable = new Variable("predicate");
           Variable objectVariable    = new Variable("object");
 
-          List<Object> selectList = new ArrayList<Object>(3);
+          List<SelectElement> selectList = new ArrayList<SelectElement>(3);
           selectList.add(subjectVariable);
           selectList.add(predicateVariable);
           selectList.add(objectVariable);
@@ -897,7 +898,7 @@
 
           session1.commit();
 
-          selectList = new ArrayList<Object>(3);
+          selectList = new ArrayList<SelectElement>(3);
           selectList.add(subjectVariable);
           selectList.add(predicateVariable);
           selectList.add(objectVariable);
@@ -940,7 +941,7 @@
           session1.removeModel(model3URI);
           session1.createModel(model3URI, null);
 
-          selectList = new ArrayList<Object>(3);
+          selectList = new ArrayList<SelectElement>(3);
           selectList.add(subjectVariable);
           selectList.add(predicateVariable);
           selectList.add(objectVariable);
@@ -982,7 +983,7 @@
 
           session1.setAutoCommit(true);
 
-          selectList = new ArrayList<Object>(3);
+          selectList = new ArrayList<SelectElement>(3);
           selectList.add(subjectVariable);
           selectList.add(predicateVariable);
           selectList.add(objectVariable);
@@ -1035,7 +1036,7 @@
           Variable predicateVariable = new Variable("predicate");
           Variable objectVariable    = new Variable("object");
 
-          List<Object> selectList = new ArrayList<Object>(3);
+          List<SelectElement> selectList = new ArrayList<SelectElement>(3);
           selectList.add(subjectVariable);
           selectList.add(predicateVariable);
           selectList.add(objectVariable);
@@ -1158,7 +1159,7 @@
         Variable predicateVariable = new Variable("predicate");
         Variable objectVariable    = new Variable("object");
 
-        List<Object> selectList = new ArrayList<Object>(3);
+        List<SelectElement> selectList = new ArrayList<SelectElement>(3);
         selectList.add(subjectVariable);
         selectList.add(predicateVariable);
         selectList.add(objectVariable);
@@ -1220,7 +1221,7 @@
                 Variable predicateVariable = new Variable("predicate");
                 Variable objectVariable    = new Variable("object");
 
-                List<Object> selectList = new ArrayList<Object>(3);
+                List<SelectElement> selectList = new ArrayList<SelectElement>(3);
                 selectList.add(subjectVariable);
                 selectList.add(predicateVariable);
                 selectList.add(objectVariable);
@@ -1342,7 +1343,7 @@
                 Variable predicateVariable = new Variable("predicate");
                 Variable objectVariable    = new Variable("object");
 
-                List<Object> selectList = new ArrayList<Object>(3);
+                List<SelectElement> selectList = new ArrayList<SelectElement>(3);
                 selectList.add(subjectVariable);
                 selectList.add(predicateVariable);
                 selectList.add(objectVariable);
@@ -1398,7 +1399,7 @@
                 Variable predicateVariable = new Variable("predicate");
                 Variable objectVariable    = new Variable("object");
 
-                List<Object> selectList = new ArrayList<Object>(3);
+                List<SelectElement> selectList = new ArrayList<SelectElement>(3);
                 selectList.add(subjectVariable);
                 selectList.add(predicateVariable);
                 selectList.add(objectVariable);
@@ -1482,7 +1483,7 @@
         Variable varB   = new Variable("b");
         Variable varT = new Variable("t");
 
-        List<Object> selectList = new ArrayList<Object>(2);
+        List<SelectElement> selectList = new ArrayList<SelectElement>(2);
         selectList.add(varA);
         selectList.add(varT);
 

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -187,7 +187,7 @@
         Variable predicateVariable = new Variable("predicate");
         Variable objectVariable    = new Variable("object");
 
-        List<Object> selectList = new ArrayList<Object>(3);
+        List<SelectElement> selectList = new ArrayList<SelectElement>(3);
         selectList.add(subjectVariable);
         selectList.add(predicateVariable);
         selectList.add(objectVariable);
@@ -276,7 +276,7 @@
         Variable predicateVariable = new Variable("predicate");
         Variable objectVariable    = new Variable("object");
 
-        List<Object> selectList = new ArrayList<Object>(3);
+        List<SelectElement> selectList = new ArrayList<SelectElement>(3);
         selectList.add(subjectVariable);
         selectList.add(predicateVariable);
         selectList.add(objectVariable);

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -16,9 +16,11 @@
  * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
  * Plugged In Software Pty Ltd. All Rights Reserved.
  *
- * Contributor(s): N/A.
+ * Contributor(s):
  *   SymbolicTransformation refactor contributed by Netymon Pty Ltd on behalf of
  *   The Australian Commonwealth Government under contract 4500507038.
+ *   External XAResource contributed by Netymon Pty Ltd on behalf of Topaz
+ *   Foundation under contract.
  *
  * [NOTE: The text of this Exhibit A may differ slightly from the text
  * of the notices in the Source Code files of the Original Code. You
@@ -34,6 +36,9 @@
 import java.net.URI;
 import java.util.*;
 
+// Java 2 enterprise packages
+import javax.transaction.xa.XAResource;
+
 // Third party packages
 import org.apache.log4j.Logger;
 import org.jrdf.graph.*;
@@ -113,6 +118,10 @@
   /** Source of transactions.  */
   private final MulgaraTransactionManager transactionManager;
 
+  private MulgaraTransactionFactory transactionFactory;
+  private MulgaraInternalTransactionFactory internalFactory;
+  private MulgaraExternalTransactionFactory externalFactory;
+
   /** The name of the rule loader to use */
   private String ruleLoaderClassName;
 
@@ -226,6 +235,9 @@
     this.temporaryModelTypeURI      = temporaryModelTypeURI;
     this.ruleLoaderClassName        = ruleLoaderClassName;
 
+    this.transactionFactory = null;
+    this.internalFactory = null;
+
     if (logger.isDebugEnabled()) {
       logger.debug("Constructed DatabaseSession");
     }
@@ -297,7 +309,7 @@
   // Methods implementing Session
   //
 
-  public void insert(URI modelURI, Set<Triple> statements) throws QueryException {
+  public void insert(URI modelURI, Set<? extends Triple> statements) throws QueryException {
     modify(modelURI, statements, ASSERT_STATEMENTS);
   }
 
@@ -305,7 +317,7 @@
     modify(modelURI, query, ASSERT_STATEMENTS);
   }
 
-  public void delete(URI modelURI, Set<Triple> statements) throws QueryException {
+  public void delete(URI modelURI, Set<? extends Triple> statements) throws QueryException {
     modify(modelURI, statements, DENY_STATEMENTS);
   }
 
@@ -510,8 +522,9 @@
     if (logger.isInfoEnabled()) {
       logger.info("setAutoCommit(" + autoCommit + ") called.");
     }
+    assertInternallyManagedXA();
     try {
-      transactionManager.setAutoCommit(this, autoCommit);
+      internalFactory.setAutoCommit(this, autoCommit);
     } catch (MulgaraTransactionException em) {
       throw new QueryException("Error setting autocommit", em);
     }
@@ -519,8 +532,9 @@
 
   public void commit() throws QueryException {
     logger.info("Committing transaction");
+    assertInternallyManagedXA();
     try {
-      transactionManager.commit(this);
+      internalFactory.commit(this);
     } catch (MulgaraTransactionException em) {
       throw new QueryException("Error performing commit", em);
     }
@@ -528,8 +542,9 @@
 
   public void rollback() throws QueryException {
     logger.info("Rollback transaction");
+    assertInternallyManagedXA();
     try {
-      transactionManager.rollback(this);
+      internalFactory.rollback(this);
     } catch (MulgaraTransactionException em) {
       throw new QueryException("Error performing rollback", em);
     }
@@ -538,9 +553,11 @@
   public void close() throws QueryException {
     logger.info("Closing session");
     try {
-      transactionManager.rollbackCurrentTransactions(this);
-    } catch (MulgaraTransactionException em) {
-      throw new QueryException("Error closing session. Forced close required", em);
+      transactionManager.closingSession(this);
+      transactionFactory = null;
+    } catch (MulgaraTransactionException em2) {
+      logger.error("Error force-closing session", em2);
+      throw new QueryException("Error force-closing session.", em2);
     }
   }
 
@@ -574,25 +591,23 @@
    */
   private synchronized void backup(OutputStream outputStream, URI serverURI, URI destinationURI)
       throws QueryException {
-    execute(
-        new BackupOperation(outputStream, serverURI, destinationURI),
+    execute(new BackupOperation(outputStream, serverURI, destinationURI),
         "Unable to backup to " + destinationURI);
   }
 
   //
   // Internal utility methods.
   //
-  protected void modify(URI modelURI, Set<Triple> statements, boolean insert) throws QueryException
+  protected void modify(URI modelURI, Set<? extends Triple> statements, boolean insert) throws QueryException
   {
     if (logger.isInfoEnabled()) {
-      logger.info("Inserting statements into " + modelURI);
+      logger.info("Modifying (ins:" + insert + ") : " + modelURI);
     }
     if (logger.isDebugEnabled()) {
-      logger.debug("Inserting statements: " + statements);
+      logger.debug("Modifying statements: " + statements);
     }
 
-    execute(new ModifyModelOperation(modelURI, statements, insert),
-            "Could not commit insert");
+    execute(new ModifyModelOperation(modelURI, statements, insert), "Could not commit modify");
   }
 
   private void modify(URI modelURI, Query query, boolean insert) throws QueryException
@@ -602,7 +617,7 @@
     }
 
     execute(new ModifyModelOperation(modelURI, query, insert, this),
-            "Unable to modify " + modelURI);
+        "Unable to modify " + modelURI);
   }
 
   /**
@@ -613,9 +628,10 @@
    */
   private void execute(Operation operation, String errorString) throws QueryException
   {
+    ensureTransactionFactorySelected();
     try {
       MulgaraTransaction transaction =
-          transactionManager.getTransaction(this, operation.isWriteOperation());
+          transactionFactory.getTransaction(this, operation.isWriteOperation());
       transaction.execute(operation, resolverSessionFactory, metadata);
     } catch (MulgaraTransactionException em) {
       logger.info("Error executing operation: " + errorString, em);
@@ -636,4 +652,36 @@
         systemResolverFactory,
         writing);
   }
+
+  private void ensureTransactionFactorySelected() throws QueryException {
+    if (transactionFactory == null) {
+      assertInternallyManagedXA();
+    }
+  }
+
+  private void assertInternallyManagedXA() throws QueryException {
+    if (transactionFactory == null) {
+      transactionFactory = internalFactory = transactionManager.getInternalFactory();
+    } else if (internalFactory == null) {
+      throw new QueryException("Attempt to use internal transaction control in externally managed session");
+    }
+  }
+
+  private void assertExternallyManagedXA() throws QueryException {
+    if (transactionFactory == null) {
+      transactionFactory = externalFactory = transactionManager.getExternalFactory();
+    } else if (externalFactory == null) {
+      throw new QueryException("Attempt to use external transaction control in internally managed session");
+    }
+  }
+
+  public XAResource getXAResource() throws QueryException {
+    assertExternallyManagedXA();
+    return externalFactory.getXAResource(this, true);
+  }
+
+  public XAResource getReadOnlyXAResource() throws QueryException {
+    assertExternallyManagedXA();
+    return externalFactory.getXAResource(this, false);
+  }
 }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionListQueryUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionListQueryUnitTest.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionListQueryUnitTest.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -282,7 +282,7 @@
   private Query selectAll(URI modelURI) throws Exception {
 
     //select $s $p $o
-    ConstraintElement [] vars = new Variable[] {
+    Variable[] vars = new Variable[] {
         StatementStore.VARIABLES[0],
         StatementStore.VARIABLES[1],
         StatementStore.VARIABLES[2]
@@ -295,7 +295,7 @@
     ConstraintImpl varConstraint = new ConstraintImpl(vars[0], vars[1], vars[2]);
 
     return new Query(
-        Arrays.asList((Object[])vars), // variable list
+        Arrays.asList((SelectElement[])vars), // variable list
         model, // model expression
         varConstraint, // constraint expr
         null, // no having

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionUnitTest.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionUnitTest.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -558,7 +558,7 @@
 
         // Evaluate the query
         Answer answer = new ArrayAnswer(session.query(new Query(
-          (List<Object>)(List)test.selectList,  // SELECT
+          test.selectList,          // SELECT
           test.model,               // FROM
           test.query,               // WHERE
           null,                     // HAVING

Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java	                        (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,2025 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com) under contract to 
+ * Topaz Foundation. Portions created under this contract are
+ * Copyright (c) 2007 Topaz Foundation
+ * All Rights Reserved.
+ *
+ * scaffolding based on AdvDatabaseSessionUnitTest.java
+ */
+
+package org.mulgara.resolver;
+
+// Java 2 standard packages
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import junit.framework.*;        // JUnit
+import org.apache.log4j.Logger;  // Log4J
+import org.jrdf.graph.SubjectNode;
+import org.jrdf.graph.PredicateNode;
+import org.jrdf.graph.ObjectNode;
+
+// Locally written packages
+import org.mulgara.query.*;
+import org.mulgara.query.rdf.Mulgara;
+import org.mulgara.query.rdf.URIReferenceImpl;
+import org.mulgara.query.rdf.TripleImpl;
+import org.mulgara.server.Session;
+import org.mulgara.util.FileUtil;
+
+/**
+ * Testing Externally Mediated Transactions. 
+ *
+ * @created 2007-11-27
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ * @company <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
+ *      Software Pty Ltd</a>
+ * @copyright &copy;2006 <a href="http://www.topazproject.org/">Topaz Project
+ * Foundation</a>
+ *
+ * @licence Open Software License v3.0</a>
+ */
+
+public class ExternalTransactionUnitTest extends TestCase
+{
+  /** Logger.  */
+  private static Logger logger =
+    Logger.getLogger(ExternalTransactionUnitTest.class.getName());
+
+  private static final URI databaseURI;
+
+  private static final URI systemModelURI;
+
+  private static final URI modelURI;
+  private static final URI model2URI;
+  private static final URI model3URI;
+  private static final URI model4URI;
+  private static final URI model5URI;
+
+  static {
+    try {
+      databaseURI    = new URI("local://database");
+      systemModelURI = new URI("local://database#");
+      modelURI       = new URI("local://database#model");
+      model2URI      = new URI("local://database#model2");
+      model3URI      = new URI("local://database#model3");
+      model4URI      = new URI("local://database#model4");
+      model5URI      = new URI("local://database#model5");
+    } catch (URISyntaxException e) {
+      throw new Error("Bad hardcoded URI", e);
+    }
+  }
+
+  private static Database database = null;
+
+  public ExternalTransactionUnitTest(String name)
+  {
+    super(name);
+  }
+
+  public static Test suite()
+  {
+    TestSuite suite = new TestSuite();
+    suite.addTest(new ExternalTransactionUnitTest("testSimpleOnePhaseCommit"));
+    suite.addTest(new ExternalTransactionUnitTest("testSimpleTwoPhaseCommit"));
+    suite.addTest(new ExternalTransactionUnitTest("testBasicQuery"));
+    suite.addTest(new ExternalTransactionUnitTest("testMultipleQuery"));
+    suite.addTest(new ExternalTransactionUnitTest("testBasicReadOnlyQuery"));
+    suite.addTest(new ExternalTransactionUnitTest("testConcurrentQuery"));
+    suite.addTest(new ExternalTransactionUnitTest("testConcurrentReadWrite"));
+    suite.addTest(new ExternalTransactionUnitTest("testSubqueryQuery"));
+    suite.addTest(new ExternalTransactionUnitTest("testConcurrentSubqueryQuery"));
+    suite.addTest(new ExternalTransactionUnitTest("testExplicitIsolationQuerySingleSession"));
+    suite.addTest(new ExternalTransactionUnitTest("testConcurrentExplicitTxn"));
+    suite.addTest(new ExternalTransactionUnitTest("testExplicitRollbackIsolationQuery"));
+    suite.addTest(new ExternalTransactionUnitTest("testExternalInternalIsolation"));
+    suite.addTest(new ExternalTransactionUnitTest("testInternalExternalIsolation"));
+    suite.addTest(new ExternalTransactionUnitTest("testInternalExternalConcurrentTxn"));
+    suite.addTest(new ExternalTransactionUnitTest("testExternalInternalConcurrentTxn"));
+    suite.addTest(new ExternalTransactionUnitTest("testInternalExternalConcurrentTxnRollback"));
+    suite.addTest(new ExternalTransactionUnitTest("testExternalInternalConcurrentTxnRollback"));
+    suite.addTest(new ExternalTransactionUnitTest("testInternalSerialMultipleSessions"));
+
+    return suite;
+  }
+
+  /**
+   * Create test objects.
+   */
+  public void setUp() throws Exception {
+    if (database == null) {
+      // Create the persistence directory
+      File persistenceDirectory =
+        new File(new File(System.getProperty("cvs.root")), "testDatabase");
+      if (persistenceDirectory.isDirectory()) {
+        if (!FileUtil.deleteDirectory(persistenceDirectory)) {
+          throw new RuntimeException(
+            "Unable to remove old directory " + persistenceDirectory
+          );
+        }
+      }
+      if (!persistenceDirectory.mkdirs()) {
+        throw new Exception("Unable to create directory "+persistenceDirectory);
+      }
+
+      // Define the the node pool factory
+      String nodePoolFactoryClassName =
+        "org.mulgara.store.nodepool.xa.XANodePoolFactory";
+
+      // Define the string pool factory
+      String stringPoolFactoryClassName =
+        "org.mulgara.store.stringpool.xa.XAStringPoolFactory";
+
+      String tempNodePoolFactoryClassName =
+        "org.mulgara.store.nodepool.memory.MemoryNodePoolFactory";
+
+      // Define the string pool factory
+      String tempStringPoolFactoryClassName =
+        "org.mulgara.store.stringpool.memory.MemoryStringPoolFactory";
+
+      // Define the resolver factory used to manage system models
+      String systemResolverFactoryClassName =
+        "org.mulgara.resolver.store.StatementStoreResolverFactory";
+
+      // Define the resolver factory used to manage system models
+      String tempResolverFactoryClassName =
+        "org.mulgara.resolver.memory.MemoryResolverFactory";
+
+      String ruleLoaderFactoryClassName =
+        "org.mulgara.rules.RuleLoaderFactory";
+
+      // Create a database which keeps its system models on the Java heap
+      database = new Database(
+                   databaseURI,
+                   persistenceDirectory,
+                   null,                            // no security domain
+                   new JotmTransactionManagerFactory(),
+                   0,                               // default transaction timeout
+                   nodePoolFactoryClassName,        // persistent
+                   new File(persistenceDirectory, "xaNodePool"),
+                   stringPoolFactoryClassName,      // persistent
+                   new File(persistenceDirectory, "xaStringPool"),
+                   systemResolverFactoryClassName,  // persistent
+                   new File(persistenceDirectory, "xaStatementStore"),
+                   tempNodePoolFactoryClassName,    // temporary nodes
+                   null,                            // no dir for temp nodes
+                   tempStringPoolFactoryClassName,  // temporary strings
+                   null,                            // no dir for temp strings
+                   tempResolverFactoryClassName,    // temporary models
+                   null,                            // no dir for temp models
+                   "",                              // no rule loader
+                   "org.mulgara.content.rdfxml.RDFXMLContentHandler");
+
+      database.addResolverFactory("org.mulgara.resolver.url.URLResolverFactory", null);
+    }
+  }
+
+
+  /**
+  * The teardown method for JUnit
+  */
+  public void tearDown()
+  {
+  }
+
+  //
+  // Test cases
+  //
+
+  private static class TestXid implements Xid {
+    private int xid;
+    public TestXid(int xid) {
+      this.xid = xid;
+    }
+    
+    public int getFormatId() {
+      return 'X';
+    }
+
+    public byte[] getBranchQualifier() {
+      return new byte[] {
+        (byte)(xid >> 0x00),
+        (byte)(xid >> 0x08)
+      };
+    }
+
+    public byte[] getGlobalTransactionId() {
+      return new byte[] {
+        (byte)(xid >> 0x10),
+        (byte)(xid >> 0x18)
+      };
+    }
+  }
+
+  /**
+   * Test the {@link DatabaseSession#create} method.
+   * As a side-effect, creates the model required by the next tests.
+   */
+  public void testSimpleOnePhaseCommit() throws URISyntaxException
+  {
+    logger.info("testSimpleOnePhaseCommit");
+
+    try {
+      DatabaseSession session = (DatabaseSession)database.newSession();
+      XAResource resource = session.getXAResource();
+      Xid xid = new TestXid(1);
+      resource.start(xid, XAResource.TMNOFLAGS);
+      try {
+        session.createModel(modelURI, null);
+        resource.end(xid, XAResource.TMSUCCESS);
+        resource.commit(xid, true);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  /**
+   * Test two phase commit.
+   * As a side-effect, loads the model required by the next tests.
+   */
+  public void testSimpleTwoPhaseCommit() throws URISyntaxException
+  {
+    logger.info("testSimpleTwoPhaseCommit");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      DatabaseSession session = (DatabaseSession)database.newSession();
+      XAResource resource = session.getXAResource();
+      Xid xid = new TestXid(1);
+      resource.start(xid, XAResource.TMNOFLAGS);
+      try {
+        session.setModel(modelURI, new ModelResource(fileURI));
+        resource.end(xid, XAResource.TMSUCCESS);
+        resource.prepare(xid);
+        resource.commit(xid, false);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  public void testBasicQuery() throws URISyntaxException {
+    logger.info("Testing basicQuery");
+
+    try {
+      // Load some test data
+      DatabaseSession session = (DatabaseSession)database.newSession();
+      try {
+        XAResource resource = session.getXAResource();
+        Xid xid = new TestXid(1);
+        resource.start(xid, XAResource.TMNOFLAGS);
+
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+        String[][] results = {
+          { "test:s01", "test:p01", "test:o01" },
+          { "test:s01", "test:p02", "test:o01" },
+          { "test:s01", "test:p02", "test:o02" },
+          { "test:s01", "test:p03", "test:o02" },
+          { "test:s02", "test:p03", "test:o02" },
+          { "test:s02", "test:p04", "test:o02" },
+          { "test:s02", "test:p04", "test:o03" },
+          { "test:s02", "test:p05", "test:o03" },
+          { "test:s03", "test:p01", "test:o01" },
+          { "test:s03", "test:p05", "test:o03" },
+          { "test:s03", "test:p06", "test:o01" },
+          { "test:s03", "test:p06", "test:o03" },
+        };
+        compareResults(results, answer);
+        answer.close();
+
+        resource.end(xid, XAResource.TMSUCCESS);
+        resource.commit(xid, true);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testMultipleQuery() throws URISyntaxException {
+    logger.info("Testing MultipleQuery");
+
+    try {
+      // Load some test data
+      Session session = database.newSession();
+      XAResource resource = session.getXAResource();
+      Xid xid = new TestXid(1);
+      resource.start(xid, XAResource.TMNOFLAGS);
+      try {
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer1 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        Answer answer2 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        compareResults(answer1, answer2);
+
+        answer1.close();
+        answer2.close();
+
+        resource.end(xid, XAResource.TMSUCCESS);
+        resource.commit(xid, true);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testBasicReadOnlyQuery() throws URISyntaxException {
+    logger.info("Testing basicReadOnlyQuery");
+
+    try {
+      // Load some test data
+      DatabaseSession session = (DatabaseSession)database.newSession();
+      try {
+        XAResource resource = session.getReadOnlyXAResource();
+        Xid xid = new TestXid(1);
+        resource.start(xid, XAResource.TMNOFLAGS);
+
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+        String[][] results = {
+          { "test:s01", "test:p01", "test:o01" },
+          { "test:s01", "test:p02", "test:o01" },
+          { "test:s01", "test:p02", "test:o02" },
+          { "test:s01", "test:p03", "test:o02" },
+          { "test:s02", "test:p03", "test:o02" },
+          { "test:s02", "test:p04", "test:o02" },
+          { "test:s02", "test:p04", "test:o03" },
+          { "test:s02", "test:p05", "test:o03" },
+          { "test:s03", "test:p01", "test:o01" },
+          { "test:s03", "test:p05", "test:o03" },
+          { "test:s03", "test:p06", "test:o01" },
+          { "test:s03", "test:p06", "test:o03" },
+        };
+        compareResults(results, answer);
+        answer.close();
+
+        resource.end(xid, XAResource.TMSUCCESS);
+        resource.commit(xid, true);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testConcurrentQuery() throws URISyntaxException {
+    logger.info("Testing concurrentQuery");
+
+    try {
+      // Load some test data
+      Session session = database.newSession();
+      XAResource resource = session.getReadOnlyXAResource();
+      Xid xid1 = new TestXid(1);
+      Xid xid2 = new TestXid(2);
+      resource.start(xid1, XAResource.TMNOFLAGS);
+      try {
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer1 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+        resource.end(xid1, XAResource.TMSUSPEND);
+        resource.start(xid2, XAResource.TMNOFLAGS);
+
+        Answer answer2 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+        resource.end(xid2, XAResource.TMSUSPEND);
+
+        compareResults(answer1, answer2);
+
+        answer1.close();
+        answer2.close();
+
+        resource.start(xid1, XAResource.TMRESUME);
+        resource.end(xid1, XAResource.TMSUCCESS);
+        resource.end(xid2, XAResource.TMSUCCESS);
+        resource.commit(xid1, true);
+        resource.commit(xid2, true);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  /**
+   * Note: What this test does is a really bad idea - there is no
+   *       isolation provided as each operation is within its own
+   *       transaction.  It does however provide a good test.
+   */
+  public void testConcurrentReadWrite() throws URISyntaxException {
+    logger.info("Testing concurrentReadWrite");
+
+    try {
+      Session session = database.newSession();
+      XAResource roResource = session.getReadOnlyXAResource();
+      XAResource rwResource = session.getXAResource();
+      Xid xid1 = new TestXid(1);
+
+      rwResource.start(xid1, XAResource.TMNOFLAGS);
+      session.createModel(model2URI, null);
+      rwResource.end(xid1, XAResource.TMSUSPEND);
+
+      try {
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        Xid xid2 = new TestXid(2);
+        roResource.start(xid2, XAResource.TMNOFLAGS);
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        roResource.end(xid2, XAResource.TMSUSPEND);
+        answer.beforeFirst();
+        while (answer.next()) {
+          rwResource.start(xid1, XAResource.TMRESUME);
+          session.insert(model2URI, Collections.singleton(new TripleImpl(
+              (SubjectNode)answer.getObject(0),
+              (PredicateNode)answer.getObject(1),
+              (ObjectNode)answer.getObject(2))));
+          rwResource.end(xid1, XAResource.TMSUSPEND);
+        }
+        answer.close();
+
+        rwResource.end(xid1, XAResource.TMSUCCESS);
+        rwResource.commit(xid1, true);
+
+        Xid xid3 = new TestXid(3);
+        roResource.start(xid3, XAResource.TMNOFLAGS);
+
+        Answer answer2 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(model2URI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        roResource.end(xid3, XAResource.TMSUSPEND);
+        String[][] results = {
+          { "test:s01", "test:p01", "test:o01" },
+          { "test:s01", "test:p02", "test:o01" },
+          { "test:s01", "test:p02", "test:o02" },
+          { "test:s01", "test:p03", "test:o02" },
+          { "test:s02", "test:p03", "test:o02" },
+          { "test:s02", "test:p04", "test:o02" },
+          { "test:s02", "test:p04", "test:o03" },
+          { "test:s02", "test:p05", "test:o03" },
+          { "test:s03", "test:p01", "test:o01" },
+          { "test:s03", "test:p05", "test:o03" },
+          { "test:s03", "test:p06", "test:o01" },
+          { "test:s03", "test:p06", "test:o03" },
+        };
+        compareResults(results, answer2);
+        answer2.close();
+
+        Xid xid4 = new TestXid(4);
+        rwResource.start(xid4, XAResource.TMNOFLAGS);
+        session.removeModel(model2URI);
+        rwResource.end(xid4, XAResource.TMSUCCESS);
+        rwResource.commit(xid4, true);
+
+        roResource.end(xid2, XAResource.TMSUCCESS);
+        roResource.commit(xid2, true);
+        roResource.end(xid3, XAResource.TMSUCCESS);
+        roResource.commit(xid3, true);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testSubqueryQuery() throws URISyntaxException {
+    logger.info("Testing subqueryQuery");
+
+    try {
+      // Load some test data
+      Session session = database.newSession();
+      XAResource roResource = session.getReadOnlyXAResource();
+      Xid xid1 = new TestXid(1);
+      roResource.start(xid1, XAResource.TMNOFLAGS);
+
+      try {
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(new Subquery(new Variable("k0"), new Query(
+          Collections.singletonList(objectVariable),
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(objectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        )));
+
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+              new URIReferenceImpl(new URI("test:p03")),
+              objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        roResource.end(xid1, XAResource.TMSUSPEND);
+
+        answer.beforeFirst();
+
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:s01")),
+            answer.getObject(0));
+        Answer sub1 = (Answer)answer.getObject(1);
+        compareResults(new String[][] { new String[] { "test:o01" },
+                                        new String[] { "test:o02" } }, sub1);
+        sub1.close();
+
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:s02")),
+            answer.getObject(0));
+        Answer sub2 = (Answer)answer.getObject(1);
+        compareResults(new String[][] { new String[] { "test:o02" },
+                                        new String[] { "test:o03" } }, sub2);
+        // Leave sub2 open.
+
+        assertFalse(answer.next());
+        answer.close();
+        sub2.close();
+
+        // Leave transaction to be closed on session close.
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testConcurrentSubqueryQuery() throws URISyntaxException {
+    logger.info("Testing concurrentSubqueryQuery");
+
+    try {
+      Session session = database.newSession();
+      XAResource rwResource = session.getXAResource();
+      Xid xid1 = new TestXid(1);
+      rwResource.start(xid1, XAResource.TMNOFLAGS);
+
+      try {
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(new Subquery(new Variable("k0"), new Query(
+          Collections.singletonList(objectVariable),
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(objectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        )));
+
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+              new URIReferenceImpl(new URI("test:p03")),
+              objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        answer.beforeFirst();
+
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:s01")),
+            answer.getObject(0));
+        Answer sub1 = (Answer)answer.getObject(1);
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:s02")),
+            answer.getObject(0));
+        Answer sub2 = (Answer)answer.getObject(1);
+        assertFalse(answer.next());
+
+        assertEquals(1, sub1.getNumberOfVariables());
+        assertEquals(1, sub2.getNumberOfVariables());
+        sub1.beforeFirst();
+        sub2.beforeFirst();
+        assertTrue(sub1.next());
+        assertTrue(sub2.next());
+        assertEquals(new URIReferenceImpl(new URI("test:o01")), sub1.getObject(0));
+        assertEquals(new URIReferenceImpl(new URI("test:o02")), sub2.getObject(0));
+
+        rwResource.end(xid1, XAResource.TMSUSPEND);
+
+        assertTrue(sub1.next());
+        assertTrue(sub2.next());
+        assertEquals(new URIReferenceImpl(new URI("test:o02")), sub1.getObject(0));
+        assertEquals(new URIReferenceImpl(new URI("test:o03")), sub2.getObject(0));
+        assertFalse(sub1.next());
+        assertFalse(sub2.next());
+
+        answer.close();
+
+        rwResource.end(xid1, XAResource.TMSUCCESS);
+        rwResource.commit(xid1, true);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testExplicitIsolationQuerySingleSession() throws URISyntaxException
+  {
+    logger.info("testExplicitIsolationQuery");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session = database.newSession();
+      try {
+        XAResource roResource = session.getReadOnlyXAResource();
+        XAResource rwResource = session.getXAResource();
+        Xid xid1 = new TestXid(1); // Initial create model.
+        Xid xid2 = new TestXid(2); // Started before setModel.
+        Xid xid3 = new TestXid(3); // setModel.
+        Xid xid4 = new TestXid(4); // Started before setModel prepares
+        Xid xid5 = new TestXid(5); // Started before setModel commits
+        Xid xid6 = new TestXid(6); // Started after setModel commits
+        Xid xid7 = new TestXid(7); // Final remove model.
+
+        rwResource.start(xid1, XAResource.TMNOFLAGS);
+        session.createModel(model3URI, null);
+        rwResource.end(xid1, XAResource.TMSUCCESS);
+        rwResource.commit(xid1, true);
+
+        // Nothing visible.
+        roResource.start(xid2, XAResource.TMNOFLAGS);
+        assertChangeNotVisible(session);
+        roResource.end(xid2, XAResource.TMSUSPEND);
+
+        // Perform update
+        rwResource.start(xid3, XAResource.TMNOFLAGS);
+        session.setModel(model3URI, new ModelResource(fileURI));
+        rwResource.end(xid3, XAResource.TMSUSPEND);
+
+        // Check uncommitted change not visible
+        roResource.start(xid4, XAResource.TMNOFLAGS);
+        assertChangeNotVisible(session);
+        roResource.end(xid4, XAResource.TMSUSPEND);
+
+        // Check original phase unaffected.
+        roResource.start(xid2, XAResource.TMRESUME);
+        assertChangeNotVisible(session);
+        roResource.end(xid2, XAResource.TMSUSPEND);
+
+        // Check micro-commit visible to current-phase
+        rwResource.start(xid3, XAResource.TMRESUME);
+        assertChangeVisible(session);
+        // Perform prepare
+        rwResource.end(xid3, XAResource.TMSUCCESS);
+        rwResource.prepare(xid3);
+
+        // Check original phase unaffected
+        roResource.start(xid2, XAResource.TMRESUME);
+        assertChangeNotVisible(session);
+        roResource.end(xid2, XAResource.TMSUSPEND);
+
+        // Check pre-prepare phase unaffected
+        roResource.start(xid4, XAResource.TMRESUME);
+        assertChangeNotVisible(session);
+        roResource.end(xid4, XAResource.TMSUSPEND);
+
+        // Check committed phase unaffected.
+        roResource.start(xid5, XAResource.TMNOFLAGS);
+        assertChangeNotVisible(session);
+        roResource.end(xid5, XAResource.TMSUSPEND);
+
+        // Do commit
+        rwResource.commit(xid3, false);
+
+        // Check original phase
+        roResource.start(xid2, XAResource.TMRESUME);
+        assertChangeNotVisible(session);
+        roResource.end(xid2, XAResource.TMSUSPEND);
+
+        // Check pre-prepare
+        roResource.start(xid4, XAResource.TMRESUME);
+        assertChangeNotVisible(session);
+        roResource.end(xid4, XAResource.TMSUSPEND);
+
+        // Check pre-commit
+        roResource.start(xid5, XAResource.TMRESUME);
+        assertChangeNotVisible(session);
+        roResource.end(xid5, XAResource.TMSUSPEND);
+
+        // Check committed phase is now updated
+        roResource.start(xid6, XAResource.TMNOFLAGS);
+        assertChangeVisible(session);
+
+        // Cleanup transactions.
+        roResource.end(xid6, XAResource.TMSUCCESS);
+        roResource.end(xid2, XAResource.TMSUCCESS);
+        roResource.end(xid4, XAResource.TMSUCCESS);
+        roResource.end(xid5, XAResource.TMSUCCESS);
+        roResource.commit(xid2, true);
+        roResource.commit(xid4, true);
+        roResource.commit(xid5, true);
+        roResource.commit(xid6, true);
+
+        // Cleanup database
+        rwResource.start(xid7, XAResource.TMNOFLAGS);
+        session.removeModel(model3URI);
+        rwResource.end(xid7, XAResource.TMSUCCESS);
+        rwResource.commit(xid7, true);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testExternalInternalIsolation() throws URISyntaxException
+  {
+    logger.info("testExplicitIsolationQuery");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        Session session2 = database.newSession();
+        try {
+          XAResource roResource = session1.getReadOnlyXAResource();
+          XAResource rwResource = session1.getXAResource();
+          Xid xid1 = new TestXid(1); // Initial create model.
+          Xid xid2 = new TestXid(2); // Main Test.
+          Xid xid3 = new TestXid(3); // Cleanup test.
+
+          rwResource.start(xid1, XAResource.TMNOFLAGS);
+          session1.createModel(model3URI, null);
+          rwResource.end(xid1, XAResource.TMSUCCESS);
+          rwResource.commit(xid1, true);
+
+          // Nothing visible.
+          assertChangeNotVisible(session2);
+
+          // Perform update
+          rwResource.start(xid2, XAResource.TMNOFLAGS);
+          session1.setModel(model3URI, new ModelResource(fileURI));
+          rwResource.end(xid2, XAResource.TMSUSPEND);
+
+          // Check uncommitted change not visible
+          assertChangeNotVisible(session2);
+
+          // Check micro-commit visible to current-phase
+          rwResource.start(xid2, XAResource.TMRESUME);
+          assertChangeVisible(session1);
+          // Perform prepare
+          rwResource.end(xid2, XAResource.TMSUCCESS);
+          rwResource.prepare(xid2);
+
+          // Check original phase unaffected
+          assertChangeNotVisible(session2);
+
+          // Do commit
+          rwResource.commit(xid2, false);
+
+          // Check committed phase is now updated
+          assertChangeVisible(session2);
+
+          // Cleanup database
+          session2.removeModel(model3URI);
+        } finally {
+          session2.close();
+        }
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testInternalExternalIsolation() throws URISyntaxException
+  {
+    logger.info("testExplicitIsolationQuery");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        Session session2 = database.newSession();
+        try {
+          XAResource roResource = session2.getReadOnlyXAResource();
+          XAResource rwResource = session2.getXAResource();
+          Xid xid1 = new TestXid(1); // Pre-update
+          Xid xid2 = new TestXid(2); // Post-update/Pre-commit
+          Xid xid3 = new TestXid(3); // Post-commit
+
+          session1.createModel(model3URI, null);
+
+          // Nothing visible.
+          roResource.start(xid1, XAResource.TMNOFLAGS);
+          assertChangeNotVisible(session2);
+          roResource.end(xid1, XAResource.TMSUSPEND);
+
+          // Perform update with autocommit off
+          session1.setAutoCommit(false);
+          session1.setModel(model3URI, new ModelResource(fileURI));
+
+          // Check uncommitted change not visible
+          roResource.start(xid2, XAResource.TMNOFLAGS);
+          assertChangeNotVisible(session2);
+          roResource.end(xid2, XAResource.TMSUSPEND);
+
+          // Check original phase unaffected.
+          roResource.start(xid1, XAResource.TMRESUME);
+          assertChangeNotVisible(session2);
+          roResource.end(xid1, XAResource.TMSUSPEND);
+
+          // Check micro-commit visible to current-phase
+          assertChangeVisible(session1);
+          session1.setAutoCommit(true);
+
+          // Check original phase unaffected
+          roResource.start(xid1, XAResource.TMRESUME);
+          assertChangeNotVisible(session2);
+          roResource.end(xid1, XAResource.TMSUSPEND);
+
+          // Check pre-commit phase unaffected
+          roResource.start(xid2, XAResource.TMRESUME);
+          assertChangeNotVisible(session2);
+          roResource.end(xid2, XAResource.TMSUSPEND);
+
+          // Check committed phase is now updated and write-lock available
+          rwResource.start(xid3, XAResource.TMNOFLAGS);
+          assertChangeVisible(session2);
+          
+          // Check internal transaction read-only
+          assertChangeVisible(session1);
+
+          // Cleanup transactions.
+          rwResource.end(xid3, XAResource.TMSUCCESS);
+          roResource.end(xid2, XAResource.TMSUCCESS);
+          roResource.end(xid1, XAResource.TMSUCCESS);
+          roResource.commit(xid1, true);
+          roResource.commit(xid2, true);
+          rwResource.commit(xid3, true);
+
+          // Cleanup database (check write-lock available again)
+          session1.removeModel(model3URI);
+        } finally {
+          session2.close();
+        }
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  private void assertChangeVisible(Session session) throws Exception {
+    Variable subjectVariable   = new Variable("subject");
+    Variable predicateVariable = new Variable("predicate");
+    Variable objectVariable    = new Variable("object");
+
+    List selectList = new ArrayList(3);
+    selectList.add(subjectVariable);
+    selectList.add(predicateVariable);
+    selectList.add(objectVariable);
+
+    // Evaluate the query
+    Answer answer = session.query(new Query(
+      selectList,                                       // SELECT
+      new ModelResource(model3URI),                      // FROM
+      new ConstraintImpl(subjectVariable,               // WHERE
+                     predicateVariable,
+                     objectVariable),
+      null,                                             // HAVING
+      Arrays.asList(new Order[] {                       // ORDER BY
+        new Order(subjectVariable, true),
+        new Order(predicateVariable, true),
+        new Order(objectVariable, true)
+      }),
+      null,                                             // LIMIT
+      0,                                                // OFFSET
+      new UnconstrainedAnswer()                         // GIVEN
+    ));
+
+    String[][] results = {
+      { "test:s01", "test:p01", "test:o01" },
+      { "test:s01", "test:p02", "test:o01" },
+      { "test:s01", "test:p02", "test:o02" },
+      { "test:s01", "test:p03", "test:o02" },
+      { "test:s02", "test:p03", "test:o02" },
+      { "test:s02", "test:p04", "test:o02" },
+      { "test:s02", "test:p04", "test:o03" },
+      { "test:s02", "test:p05", "test:o03" },
+      { "test:s03", "test:p01", "test:o01" },
+      { "test:s03", "test:p05", "test:o03" },
+      { "test:s03", "test:p06", "test:o01" },
+      { "test:s03", "test:p06", "test:o03" },
+    };
+    compareResults(results, answer);
+    answer.close();
+  }
+
+  private void assertChangeNotVisible(Session session) throws Exception {
+    Variable subjectVariable   = new Variable("subject");
+    Variable predicateVariable = new Variable("predicate");
+    Variable objectVariable    = new Variable("object");
+
+    List selectList = new ArrayList(3);
+    selectList.add(subjectVariable);
+    selectList.add(predicateVariable);
+    selectList.add(objectVariable);
+
+    // Evaluate the query
+    Answer answer = session.query(new Query(
+      selectList,                                       // SELECT
+      new ModelResource(model3URI),                      // FROM
+      new ConstraintImpl(subjectVariable,               // WHERE
+                     predicateVariable,
+                     objectVariable),
+      null,                                             // HAVING
+      Arrays.asList(new Order[] {                       // ORDER BY
+        new Order(subjectVariable, true),
+        new Order(predicateVariable, true),
+        new Order(objectVariable, true)
+      }),
+      null,                                             // LIMIT
+      0,                                                // OFFSET
+      new UnconstrainedAnswer()                         // GIVEN
+    ));
+    answer.beforeFirst();
+    assertFalse(answer.next());
+    answer.close();
+  }
+
+  /**
+   * Test two simultaneous, explicit transactions, in two threads. The second one should block
+   * until the first one sets auto-commit back to true.
+   */
+  public void testConcurrentExplicitTxn() throws URISyntaxException
+  {
+    logger.info("testConcurrentExplicitTxn");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        XAResource resource1 = session1.getXAResource();
+        resource1.start(new TestXid(1), XAResource.TMNOFLAGS);
+        session1.createModel(model3URI, null);
+        resource1.end(new TestXid(1), XAResource.TMSUCCESS);
+        resource1.commit(new TestXid(1), true);
+
+        resource1.start(new TestXid(2), XAResource.TMNOFLAGS);
+        session1.setModel(model3URI, new ModelResource(fileURI));
+
+        final boolean[] tx2Started = new boolean[] { false };
+
+        Thread t2 = new Thread("tx2Test") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              XAResource resource2 = session2.getXAResource();
+              try {
+                resource2.start(new TestXid(3), XAResource.TMNOFLAGS);
+
+                synchronized (tx2Started) {
+                  tx2Started[0] = true;
+                  tx2Started.notify();
+                }
+
+                Variable subjectVariable   = new Variable("subject");
+                Variable predicateVariable = new Variable("predicate");
+                Variable objectVariable    = new Variable("object");
+
+                List selectList = new ArrayList(3);
+                selectList.add(subjectVariable);
+                selectList.add(predicateVariable);
+                selectList.add(objectVariable);
+
+                // Evaluate the query
+                Answer answer = session2.query(new Query(
+                  selectList,                                       // SELECT
+                  new ModelResource(model3URI),                      // FROM
+                  new ConstraintImpl(subjectVariable,               // WHERE
+                                 predicateVariable,
+                                 objectVariable),
+                  null,                                             // HAVING
+                  Arrays.asList(new Order[] {                       // ORDER BY
+                    new Order(subjectVariable, true),
+                    new Order(predicateVariable, true),
+                    new Order(objectVariable, true)
+                  }),
+                  null,                                             // LIMIT
+                  0,                                                // OFFSET
+                  new UnconstrainedAnswer()                         // GIVEN
+                ));
+
+                String[][] results = {
+                  { "test:s01", "test:p01", "test:o01" },
+                  { "test:s01", "test:p02", "test:o01" },
+                  { "test:s01", "test:p02", "test:o02" },
+                  { "test:s01", "test:p03", "test:o02" },
+                  { "test:s02", "test:p03", "test:o02" },
+                  { "test:s02", "test:p04", "test:o02" },
+                  { "test:s02", "test:p04", "test:o03" },
+                  { "test:s02", "test:p05", "test:o03" },
+                  { "test:s03", "test:p01", "test:o01" },
+                  { "test:s03", "test:p05", "test:o03" },
+                  { "test:s03", "test:p06", "test:o01" },
+                  { "test:s03", "test:p06", "test:o03" },
+                };
+                compareResults(results, answer);
+                answer.close();
+
+                resource2.end(new TestXid(3), XAResource.TMSUCCESS);
+                resource2.commit(new TestXid(3), true);
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        resource1.commit(new TestXid(2), true);
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+            assertTrue("second transaction should've started", tx2Started[0]);
+          }
+        }
+
+        try {
+          t2.join(2000L);
+        } catch (InterruptedException ie) {
+          logger.error("wait for tx2-terminated interrupted", ie);
+          fail(ie);
+        }
+        assertFalse("second transaction should've terminated", t2.isAlive());
+
+        resource1.start(new TestXid(4), XAResource.TMNOFLAGS);
+        session1.removeModel(model3URI);
+        resource1.end(new TestXid(4), XAResource.TMSUCCESS);
+        resource1.commit(new TestXid(4), true);
+
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  /**
+   * Test two simultaneous transactions, in two threads. The second one should block
+   * until the first one sets auto-commit back to true.
+   */
+  public void testExternalInternalConcurrentTxn() throws URISyntaxException
+  {
+    logger.info("testConcurrentExplicitTxn");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        XAResource resource1 = session1.getXAResource();
+        resource1.start(new TestXid(1), XAResource.TMNOFLAGS);
+        session1.createModel(model3URI, null);
+        resource1.end(new TestXid(1), XAResource.TMSUCCESS);
+        resource1.commit(new TestXid(1), true);
+
+        resource1.start(new TestXid(2), XAResource.TMNOFLAGS);
+        session1.setModel(model3URI, new ModelResource(fileURI));
+
+        final boolean[] tx2Started = new boolean[] { false };
+
+        Thread t2 = new Thread("tx2Test") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              try {
+                session2.setAutoCommit(false);
+
+                synchronized (tx2Started) {
+                  tx2Started[0] = true;
+                  tx2Started.notify();
+                }
+
+                Variable subjectVariable   = new Variable("subject");
+                Variable predicateVariable = new Variable("predicate");
+                Variable objectVariable    = new Variable("object");
+
+                List selectList = new ArrayList(3);
+                selectList.add(subjectVariable);
+                selectList.add(predicateVariable);
+                selectList.add(objectVariable);
+
+                // Evaluate the query
+                Answer answer = session2.query(new Query(
+                  selectList,                                       // SELECT
+                  new ModelResource(model3URI),                      // FROM
+                  new ConstraintImpl(subjectVariable,               // WHERE
+                                 predicateVariable,
+                                 objectVariable),
+                  null,                                             // HAVING
+                  Arrays.asList(new Order[] {                       // ORDER BY
+                    new Order(subjectVariable, true),
+                    new Order(predicateVariable, true),
+                    new Order(objectVariable, true)
+                  }),
+                  null,                                             // LIMIT
+                  0,                                                // OFFSET
+                  new UnconstrainedAnswer()                         // GIVEN
+                ));
+
+                String[][] results = {
+                  { "test:s01", "test:p01", "test:o01" },
+                  { "test:s01", "test:p02", "test:o01" },
+                  { "test:s01", "test:p02", "test:o02" },
+                  { "test:s01", "test:p03", "test:o02" },
+                  { "test:s02", "test:p03", "test:o02" },
+                  { "test:s02", "test:p04", "test:o02" },
+                  { "test:s02", "test:p04", "test:o03" },
+                  { "test:s02", "test:p05", "test:o03" },
+                  { "test:s03", "test:p01", "test:o01" },
+                  { "test:s03", "test:p05", "test:o03" },
+                  { "test:s03", "test:p06", "test:o01" },
+                  { "test:s03", "test:p06", "test:o03" },
+                };
+                compareResults(results, answer);
+                answer.close();
+
+                session2.setAutoCommit(true);
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        resource1.commit(new TestXid(2), true);
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+            assertTrue("second transaction should've started", tx2Started[0]);
+          }
+        }
+
+        try {
+          t2.join(2000L);
+        } catch (InterruptedException ie) {
+          logger.error("wait for tx2-terminated interrupted", ie);
+          fail(ie);
+        }
+        assertFalse("second transaction should've terminated", t2.isAlive());
+
+        resource1.start(new TestXid(4), XAResource.TMNOFLAGS);
+        session1.removeModel(model3URI);
+        resource1.end(new TestXid(4), XAResource.TMSUCCESS);
+        resource1.commit(new TestXid(4), true);
+
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  /**
+   * Test two simultaneous transactions, in two threads. The second one should block
+   * until the first one sets auto-commit back to true.
+   */
+  public void testInternalExternalConcurrentTxn() throws URISyntaxException
+  {
+    logger.info("testConcurrentExplicitTxn");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        session1.createModel(model3URI, null);
+
+        session1.setAutoCommit(false);
+        session1.setModel(model3URI, new ModelResource(fileURI));
+
+        final boolean[] tx2Started = new boolean[] { false };
+
+        Thread t2 = new Thread("tx2Test") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              try {
+                XAResource resource = session2.getXAResource();
+                resource.start(new TestXid(1), XAResource.TMNOFLAGS);
+
+                synchronized (tx2Started) {
+                  tx2Started[0] = true;
+                  tx2Started.notify();
+                }
+
+                Variable subjectVariable   = new Variable("subject");
+                Variable predicateVariable = new Variable("predicate");
+                Variable objectVariable    = new Variable("object");
+
+                List selectList = new ArrayList(3);
+                selectList.add(subjectVariable);
+                selectList.add(predicateVariable);
+                selectList.add(objectVariable);
+
+                // Evaluate the query
+                Answer answer = session2.query(new Query(
+                  selectList,                                       // SELECT
+                  new ModelResource(model3URI),                      // FROM
+                  new ConstraintImpl(subjectVariable,               // WHERE
+                                 predicateVariable,
+                                 objectVariable),
+                  null,                                             // HAVING
+                  Arrays.asList(new Order[] {                       // ORDER BY
+                    new Order(subjectVariable, true),
+                    new Order(predicateVariable, true),
+                    new Order(objectVariable, true)
+                  }),
+                  null,                                             // LIMIT
+                  0,                                                // OFFSET
+                  new UnconstrainedAnswer()                         // GIVEN
+                ));
+
+                String[][] results = {
+                  { "test:s01", "test:p01", "test:o01" },
+                  { "test:s01", "test:p02", "test:o01" },
+                  { "test:s01", "test:p02", "test:o02" },
+                  { "test:s01", "test:p03", "test:o02" },
+                  { "test:s02", "test:p03", "test:o02" },
+                  { "test:s02", "test:p04", "test:o02" },
+                  { "test:s02", "test:p04", "test:o03" },
+                  { "test:s02", "test:p05", "test:o03" },
+                  { "test:s03", "test:p01", "test:o01" },
+                  { "test:s03", "test:p05", "test:o03" },
+                  { "test:s03", "test:p06", "test:o01" },
+                  { "test:s03", "test:p06", "test:o03" },
+                };
+                compareResults(results, answer);
+                answer.close();
+
+                resource.end(new TestXid(1), XAResource.TMSUCCESS);
+                resource.rollback(new TestXid(1));
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        session1.commit();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        session1.setAutoCommit(true);
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+            assertTrue("second transaction should've started", tx2Started[0]);
+          }
+        }
+
+        try {
+          t2.join(2000L);
+        } catch (InterruptedException ie) {
+          logger.error("wait for tx2-terminated interrupted", ie);
+          fail(ie);
+        }
+        assertFalse("second transaction should've terminated", t2.isAlive());
+
+        session1.removeModel(model3URI);
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  /**
+   * Test two simultaneous transactions, in two threads. The second one should block
+   * until the first one sets auto-commit back to true.
+   */
+  public void testExternalInternalConcurrentTxnRollback() throws URISyntaxException
+  {
+    logger.info("testConcurrentExplicitTxn");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        XAResource resource1 = session1.getXAResource();
+        resource1.start(new TestXid(1), XAResource.TMNOFLAGS);
+        session1.createModel(model3URI, null);
+        resource1.end(new TestXid(1), XAResource.TMSUCCESS);
+        resource1.commit(new TestXid(1), true);
+
+        resource1.start(new TestXid(2), XAResource.TMNOFLAGS);
+        session1.setModel(model3URI, new ModelResource(fileURI));
+
+        final boolean[] tx2Started = new boolean[] { false };
+
+        Thread t2 = new Thread("tx2Test") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              try {
+                session2.setAutoCommit(false);
+
+                synchronized (tx2Started) {
+                  tx2Started[0] = true;
+                  tx2Started.notify();
+                }
+
+                Variable subjectVariable   = new Variable("subject");
+                Variable predicateVariable = new Variable("predicate");
+                Variable objectVariable    = new Variable("object");
+
+                List selectList = new ArrayList(3);
+                selectList.add(subjectVariable);
+                selectList.add(predicateVariable);
+                selectList.add(objectVariable);
+
+                // Evaluate the query
+                Answer answer = session2.query(new Query(
+                  selectList,                                       // SELECT
+                  new ModelResource(model3URI),                      // FROM
+                  new ConstraintImpl(subjectVariable,               // WHERE
+                                 predicateVariable,
+                                 objectVariable),
+                  null,                                             // HAVING
+                  Arrays.asList(new Order[] {                       // ORDER BY
+                    new Order(subjectVariable, true),
+                    new Order(predicateVariable, true),
+                    new Order(objectVariable, true)
+                  }),
+                  null,                                             // LIMIT
+                  0,                                                // OFFSET
+                  new UnconstrainedAnswer()                         // GIVEN
+                ));
+
+                answer.beforeFirst();
+                assertFalse(answer.next());
+                answer.close();
+
+                session2.setAutoCommit(true);
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        resource1.rollback(new TestXid(2));
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+            assertTrue("second transaction should've started", tx2Started[0]);
+          }
+        }
+
+        try {
+          t2.join(2000L);
+        } catch (InterruptedException ie) {
+          logger.error("wait for tx2-terminated interrupted", ie);
+          fail(ie);
+        }
+        assertFalse("second transaction should've terminated", t2.isAlive());
+
+        resource1.start(new TestXid(4), XAResource.TMNOFLAGS);
+        session1.removeModel(model3URI);
+        resource1.end(new TestXid(4), XAResource.TMSUCCESS);
+        resource1.commit(new TestXid(4), true);
+
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  /**
+   * Test two simultaneous transactions, in two threads. The second one should block
+   * until the first one sets auto-commit back to true.
+   */
+  public void testInternalExternalConcurrentTxnRollback() throws URISyntaxException
+  {
+    logger.info("testConcurrentExplicitTxn");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        session1.createModel(model3URI, null);
+
+        session1.setAutoCommit(false);
+        session1.setModel(model3URI, new ModelResource(fileURI));
+
+        final boolean[] tx2Started = new boolean[] { false };
+
+        Thread t2 = new Thread("tx2Test") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              try {
+                XAResource resource = session2.getXAResource();
+                resource.start(new TestXid(1), XAResource.TMNOFLAGS);
+
+                synchronized (tx2Started) {
+                  tx2Started[0] = true;
+                  tx2Started.notify();
+                }
+
+                Variable subjectVariable   = new Variable("subject");
+                Variable predicateVariable = new Variable("predicate");
+                Variable objectVariable    = new Variable("object");
+
+                List selectList = new ArrayList(3);
+                selectList.add(subjectVariable);
+                selectList.add(predicateVariable);
+                selectList.add(objectVariable);
+
+                // Evaluate the query
+                Answer answer = session2.query(new Query(
+                  selectList,                                       // SELECT
+                  new ModelResource(model3URI),                      // FROM
+                  new ConstraintImpl(subjectVariable,               // WHERE
+                                 predicateVariable,
+                                 objectVariable),
+                  null,                                             // HAVING
+                  Arrays.asList(new Order[] {                       // ORDER BY
+                    new Order(subjectVariable, true),
+                    new Order(predicateVariable, true),
+                    new Order(objectVariable, true)
+                  }),
+                  null,                                             // LIMIT
+                  0,                                                // OFFSET
+                  new UnconstrainedAnswer()                         // GIVEN
+                ));
+
+                answer.beforeFirst();
+                assertFalse(answer.next());
+                answer.close();
+
+                resource.end(new TestXid(1), XAResource.TMFAIL);
+                resource.rollback(new TestXid(1));
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        session1.rollback();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        session1.setAutoCommit(true);
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+            assertTrue("second transaction should've started", tx2Started[0]);
+          }
+        }
+
+        try {
+          t2.join(2000L);
+        } catch (InterruptedException ie) {
+          logger.error("wait for tx2-terminated interrupted", ie);
+          fail(ie);
+        }
+        assertFalse("second transaction should've terminated", t2.isAlive());
+
+        session1.removeModel(model3URI);
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  public void testExplicitRollbackIsolationQuery() throws URISyntaxException {
+    logger.info("testExplicitRollbackIsolationQuery");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session = database.newSession();
+      XAResource roResource = session.getReadOnlyXAResource();
+      XAResource rwResource = session.getXAResource();
+      try {
+        rwResource.start(new TestXid(1), XAResource.TMNOFLAGS);
+        session.createModel(model3URI, null);
+        rwResource.end(new TestXid(1), XAResource.TMSUCCESS);
+        rwResource.commit(new TestXid(1), true);
+
+        rwResource.start(new TestXid(2), XAResource.TMNOFLAGS);
+        session.setModel(model3URI, new ModelResource(fileURI));
+        rwResource.end(new TestXid(2), XAResource.TMSUSPEND);
+
+        roResource.start(new TestXid(3), XAResource.TMNOFLAGS);
+
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(model3URI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+        answer.beforeFirst();
+        assertFalse(answer.next());
+        answer.close();
+
+        roResource.end(new TestXid(3), XAResource.TMSUCCESS);
+        roResource.commit(new TestXid(3), true);
+
+        rwResource.end(new TestXid(2), XAResource.TMFAIL);
+        rwResource.rollback(new TestXid(2));
+
+        roResource.start(new TestXid(4), XAResource.TMNOFLAGS);
+        selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(model3URI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        answer.beforeFirst();
+        assertFalse(answer.next());
+        answer.close();
+
+        roResource.end(new TestXid(4), XAResource.TMFAIL);
+        roResource.rollback(new TestXid(4));
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  /**
+   * Tests cleaning up a transaction on close.  This test added in the process
+   * of fixing a bug reported by Ronald on the JTA-beta.
+   */
+  public void testInternalSerialMultipleSessions() throws URISyntaxException
+  {
+    logger.info("testInternalSerialMultipleSessions");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      Session session2 = database.newSession();
+      try {
+        session1.createModel(model4URI, null);
+
+        session1.setAutoCommit(false);
+        session1.setModel(model4URI, new ModelResource(fileURI));
+
+        session1.commit();
+        session1.close();
+
+        session2.setAutoCommit(false);
+      } finally {
+        session2.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  //
+  // Internal methods
+  //
+
+  private void compareResults(String[][] expected, Answer answer) throws Exception {
+    try {
+      answer.beforeFirst();
+      for (int i = 0; i < expected.length; i++) {
+        assertTrue("Answer short at row " + i, answer.next());
+        assertEquals(expected[i].length, answer.getNumberOfVariables());
+        for (int j = 0; j < expected[i].length; j++) {
+          URIReferenceImpl uri = new URIReferenceImpl(new URI(expected[i][j]));
+          assertEquals(uri, answer.getObject(j));
+        }
+      }
+      assertFalse(answer.next());
+    } catch (Exception e) {
+      logger.error("Failed test - " + answer);
+      answer.close();
+      throw e;
+    }
+  }
+
+  private void compareResults(Answer answer1, Answer answer2) throws Exception {
+    answer1.beforeFirst();
+    answer2.beforeFirst();
+    assertEquals(answer1.getNumberOfVariables(), answer2.getNumberOfVariables());
+    while (answer1.next()) {
+      assertTrue(answer2.next());
+      for (int i = 0; i < answer1.getNumberOfVariables(); i++) {
+        assertEquals(answer1.getObject(i), answer2.getObject(i));
+      }
+    }
+    assertFalse(answer2.next());
+  }
+
+
+  /**
+   * Fail with an unexpected exception
+   */
+  private void fail(Throwable throwable)
+  {
+    StringWriter stringWriter = new StringWriter();
+    throwable.printStackTrace(new PrintWriter(stringWriter));
+    fail(stringWriter.toString());
+  }
+}

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -126,6 +126,14 @@
                    Resolver        systemResolver)
     throws ResolverFactoryException
   {
+    if (resolver == null) {
+      throw new IllegalArgumentException("Resolver 'null'");
+    } else if (resolverSession == null) {
+      throw new IllegalArgumentException("ResolverSession 'null'");
+    } else if (systemResolver == null) {
+      throw new IllegalArgumentException("SystemResolver 'null'");
+    }
+
     // Initialize fields
     this.resolver        = resolver;
     this.rdfType         = rdfType;

Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java	                        (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,2114 @@
+/*
+ * Copyright 2008 The Topaz Foundation 
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+package org.mulgara.resolver;
+
+// Java 2 standard packages
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.RollbackException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+
+// Third party packages
+import junit.framework.*;        // JUnit
+import org.apache.log4j.Logger;  // Log4J
+import org.jrdf.graph.ObjectNode;
+import org.jrdf.graph.PredicateNode;
+import org.jrdf.graph.SubjectNode;
+import org.objectweb.jotm.Jotm;
+import org.objectweb.transaction.jta.TMService;
+
+// Locally written packages
+import org.mulgara.query.*;
+import org.mulgara.query.rdf.Mulgara;
+import org.mulgara.query.rdf.URIReferenceImpl;
+import org.mulgara.query.rdf.TripleImpl;
+import org.mulgara.server.Session;
+import org.mulgara.server.SessionFactory;
+import org.mulgara.server.driver.SessionFactoryFinder;
+import org.mulgara.util.FileUtil;
+
+import org.mulgara.query.QueryException;
+import org.mulgara.server.JRDFSession;
+import org.mulgara.server.SessionFactory;
+import org.mulgara.server.driver.SessionFactoryFinder;
+
+/**
+ * Regression test to test JTA integration with external JOTM instance.
+ *
+ * @created 2008-01-11
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ * @copyright &copy;2008 <a href="http://www.topazproject.org/">The Topaz Foundation</a>
+ * @licence Apache License v2.0
+ */
+public class JotmTransactionStandaloneTest extends TestCase
+{
+  /** Logger.  */
+  private static Logger logger =
+    Logger.getLogger(JotmTransactionStandaloneTest.class.getName());
+
+  private static final URI databaseURI;
+
+  private static final URI systemModelURI;
+
+  private static final URI modelURI;
+  private static final URI model2URI;
+  private static final URI model3URI;
+  private static final URI model4URI;
+  private static final URI model5URI;
+
+  static {
+    try {
+      databaseURI    = new URI("rmi://localhost/server1");
+      systemModelURI = new URI("rmi://localhost/server1#");
+      modelURI       = new URI("rmi://localhost/server1#jotmmodel");
+      model2URI      = new URI("rmi://localhost/server1#jotmmodel2");
+      model3URI      = new URI("rmi://localhost/server1#jotmmodel3");
+      model4URI      = new URI("rmi://localhost/server1#jotmmodel4");
+      model5URI      = new URI("rmi://localhost/server1#jotmmodel5");
+    } catch (URISyntaxException e) {
+      throw new Error("Bad hardcoded URI", e);
+    }
+  }
+
+  private static SessionFactory sessionFactory;
+  private static TMService txService;
+  private static TransactionManager txManager;
+
+  public JotmTransactionStandaloneTest(String name) {
+    super(name);
+  }
+
+  public static Test suite() {
+    TestSuite suite = new TestSuite();
+    suite.addTest(new JotmTransactionStandaloneTest("setup"));
+    suite.addTest(new JotmTransactionStandaloneTest("testTrivalExplicit"));
+    suite.addTest(new JotmTransactionStandaloneTest("testSessionCloseRollback"));
+    suite.addTest(new JotmTransactionStandaloneTest("testTrivialExplicitAgain"));
+    suite.addTest(new JotmTransactionStandaloneTest("testBasicQuery"));
+    suite.addTest(new JotmTransactionStandaloneTest("testMultipleEnlist"));
+    suite.addTest(new JotmTransactionStandaloneTest("testMultipleQuery"));
+    suite.addTest(new JotmTransactionStandaloneTest("testBasicReadOnlyQuery"));
+    suite.addTest(new JotmTransactionStandaloneTest("testConcurrentQuery"));
+    suite.addTest(new JotmTransactionStandaloneTest("testRepeatGetXAQuery"));
+    suite.addTest(new JotmTransactionStandaloneTest("testConcurrentReadWrite"));
+    suite.addTest(new JotmTransactionStandaloneTest("testSubqueryQuery"));
+    suite.addTest(new JotmTransactionStandaloneTest("testTrivalImplicit"));
+    suite.addTest(new JotmTransactionStandaloneTest("cleanup"));
+
+    return suite;
+  }
+
+
+  public void setup() throws Exception {
+    logger.info("Doing setup");
+    sessionFactory = SessionFactoryFinder.newSessionFactory(databaseURI);
+    txService = new Jotm(true, false); // local, unbound.
+    txManager = txService.getTransactionManager();
+  }
+
+  public void cleanup() throws Exception {
+    logger.info("Doing cleanup");
+    txService.stop();
+  }
+
+  //
+  // Test cases
+  //
+
+  private static class TestXid implements Xid {
+    private int xid;
+    public TestXid(int xid) {
+      this.xid = xid;
+    }
+    
+    public int getFormatId() {
+      return 'X';
+    }
+
+    public byte[] getBranchQualifier() {
+      return new byte[] {
+        (byte)(xid >> 0x00),
+        (byte)(xid >> 0x08)
+      };
+    }
+
+    public byte[] getGlobalTransactionId() {
+      return new byte[] {
+        (byte)(xid >> 0x10),
+        (byte)(xid >> 0x18)
+      };
+    }
+  }
+
+  /**
+   * Test the {@link DatabaseSession#create} method.
+   * As a side-effect, creates the model required by the next tests.
+   */
+  public void testTrivalExplicit() throws URISyntaxException {
+    logger.info("testTrivalExplicit");
+    try {
+      txManager.begin();
+      Session session = sessionFactory.newSession();
+      txManager.getTransaction().enlistResource(session.getXAResource());
+
+      try {
+        session.createModel(modelURI, null);
+        txManager.commit();
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  public void testSessionCloseRollback() throws URISyntaxException {
+    logger.info("testSessionCloseRollback");
+    try {
+      URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+      txManager.begin();
+      Session session = sessionFactory.newSession();
+      txManager.getTransaction().enlistResource(session.getXAResource());
+
+      try {
+        try {
+          session.setModel(modelURI, new ModelResource(fileURI));
+        } finally {
+          session.close();
+        }
+      } finally {
+        try {
+          txManager.commit();
+        } catch (HeuristicRollbackException eh) { // This is my expectation.
+          logger.warn("HeuristicRollback detected successfully", eh);
+        } catch (RollbackException er) {          // This would also meet the spec.
+          logger.warn("Rollback detected successfully", er);
+        } catch (Exception e) {
+          logger.warn("Exception from Jotm", e);
+          throw e;
+        }
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testTrivialExplicitAgain() throws URISyntaxException {
+    logger.info("testTrivialExplicitAgain");
+    try {
+      URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+      txManager.begin();
+      Session session = sessionFactory.newSession();
+      txManager.getTransaction().enlistResource(session.getXAResource());
+
+      try {
+        session.setModel(modelURI, new ModelResource(fileURI));
+        txManager.commit();
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testBasicQuery() throws URISyntaxException {
+    logger.info("testBasicQuery");
+
+    try {
+      txManager.begin();
+      Session session = sessionFactory.newSession();
+      try {
+        txManager.getTransaction().enlistResource(session.getXAResource());
+
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+        String[][] results = {
+          { "test:s01", "test:p01", "test:o01" },
+          { "test:s01", "test:p02", "test:o01" },
+          { "test:s01", "test:p02", "test:o02" },
+          { "test:s01", "test:p03", "test:o02" },
+          { "test:s02", "test:p03", "test:o02" },
+          { "test:s02", "test:p04", "test:o02" },
+          { "test:s02", "test:p04", "test:o03" },
+          { "test:s02", "test:p05", "test:o03" },
+          { "test:s03", "test:p01", "test:o01" },
+          { "test:s03", "test:p05", "test:o03" },
+          { "test:s03", "test:p06", "test:o01" },
+          { "test:s03", "test:p06", "test:o03" },
+        };
+        compareResults(results, answer);
+        answer.close();
+
+        txManager.commit();
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  public void testMultipleEnlist() throws URISyntaxException {
+    logger.info("testMultipleEnlist");
+
+    try {
+      txManager.begin();
+      Session session = sessionFactory.newSession();
+      try {
+        txManager.getTransaction().enlistResource(session.getXAResource());
+        txManager.getTransaction().enlistResource(session.getXAResource());
+        txManager.getTransaction().enlistResource(session.getXAResource());
+
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+        String[][] results = {
+          { "test:s01", "test:p01", "test:o01" },
+          { "test:s01", "test:p02", "test:o01" },
+          { "test:s01", "test:p02", "test:o02" },
+          { "test:s01", "test:p03", "test:o02" },
+          { "test:s02", "test:p03", "test:o02" },
+          { "test:s02", "test:p04", "test:o02" },
+          { "test:s02", "test:p04", "test:o03" },
+          { "test:s02", "test:p05", "test:o03" },
+          { "test:s03", "test:p01", "test:o01" },
+          { "test:s03", "test:p05", "test:o03" },
+          { "test:s03", "test:p06", "test:o01" },
+          { "test:s03", "test:p06", "test:o03" },
+        };
+        compareResults(results, answer);
+        answer.close();
+
+        txManager.commit();
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  public void testMultipleQuery() throws URISyntaxException {
+    logger.info("testMultipleQuery");
+
+    try {
+      txManager.begin();
+      Session session = sessionFactory.newSession();
+      txManager.getTransaction().enlistResource(session.getXAResource());
+      try {
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer1 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        Answer answer2 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        compareResults(answer1, answer2);
+
+        answer1.close();
+        answer2.close();
+
+        txManager.commit();
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testBasicReadOnlyQuery() throws URISyntaxException {
+    logger.info("testBasicReadOnlyQuery");
+
+    try {
+      txManager.begin();
+      Session session = sessionFactory.newSession();
+      try {
+        txManager.getTransaction().enlistResource(session.getReadOnlyXAResource());
+
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+        String[][] results = {
+          { "test:s01", "test:p01", "test:o01" },
+          { "test:s01", "test:p02", "test:o01" },
+          { "test:s01", "test:p02", "test:o02" },
+          { "test:s01", "test:p03", "test:o02" },
+          { "test:s02", "test:p03", "test:o02" },
+          { "test:s02", "test:p04", "test:o02" },
+          { "test:s02", "test:p04", "test:o03" },
+          { "test:s02", "test:p05", "test:o03" },
+          { "test:s03", "test:p01", "test:o01" },
+          { "test:s03", "test:p05", "test:o03" },
+          { "test:s03", "test:p06", "test:o01" },
+          { "test:s03", "test:p06", "test:o03" },
+        };
+        compareResults(results, answer);
+        answer.close();
+
+        txManager.commit();
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testConcurrentQuery() throws URISyntaxException {
+    logger.info("testConcurrentQuery");
+
+    try {
+      txManager.begin();
+      Session session = sessionFactory.newSession();
+      XAResource roResource = session.getReadOnlyXAResource();
+      Transaction tx1 = txManager.getTransaction();
+      tx1.enlistResource(roResource);
+
+      try {
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer1 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+        
+        tx1 = txManager.suspend();
+
+        txManager.begin();
+        Transaction tx2 = txManager.getTransaction();
+        tx2.enlistResource(roResource);
+
+        Answer answer2 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        tx2 = txManager.suspend();
+
+        compareResults(answer1, answer2);
+
+        answer1.close();
+        answer2.close();
+
+        txManager.resume(tx1);
+        txManager.commit();
+        // I believe JTA requires me to call end here - our implementation doesn't care.
+        tx2.commit();
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testRepeatGetXAQuery() throws URISyntaxException {
+    logger.info("testRepeatGetXAQuery");
+
+    try {
+      txManager.begin();
+      Session session = sessionFactory.newSession();
+      Transaction tx1 = txManager.getTransaction();
+      tx1.enlistResource(session.getReadOnlyXAResource());
+
+      try {
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer1 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+        
+        tx1 = txManager.suspend();
+
+        txManager.begin();
+        Transaction tx2 = txManager.getTransaction();
+        tx2.enlistResource(session.getReadOnlyXAResource());
+
+        Answer answer2 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        tx2 = txManager.suspend();
+
+        compareResults(answer1, answer2);
+
+        answer1.close();
+        answer2.close();
+
+        txManager.resume(tx1);
+        txManager.commit();
+        // I believe JTA requires me to call end here - our implementation doesn't care.
+        tx2.commit();
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  //
+  // Note: What this test does is a really bad idea - there is no
+  //       isolation provided as each operation is within its own
+  //       transaction.  It does however provide a good test.
+  //
+  public void testConcurrentReadWrite() throws URISyntaxException {
+    logger.info("testConcurrentReadWrite");
+
+    try {
+      txManager.begin();
+      Session session = sessionFactory.newSession();
+      XAResource roResource = session.getReadOnlyXAResource();
+      XAResource rwResource = session.getXAResource();
+
+      txManager.getTransaction().enlistResource(rwResource);
+      session.createModel(model2URI, null);
+      Transaction tx1 = txManager.suspend();
+
+      try {
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        txManager.begin();
+        txManager.getTransaction().enlistResource(roResource);
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        Transaction tx2 = txManager.suspend();
+
+        answer.beforeFirst();
+        while (answer.next()) {
+          txManager.resume(tx1);
+          session.insert(model2URI, Collections.singleton(new TripleImpl(
+              (SubjectNode)answer.getObject(0),
+              (PredicateNode)answer.getObject(1),
+              (ObjectNode)answer.getObject(2))));
+          tx1 = txManager.suspend();
+        }
+        answer.close();
+
+        txManager.resume(tx1);
+        txManager.commit();
+
+        txManager.begin();
+        txManager.getTransaction().enlistResource(roResource);
+
+        Answer answer2 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(model2URI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        Transaction tx3 = txManager.suspend();
+
+        String[][] results = {
+          { "test:s01", "test:p01", "test:o01" },
+          { "test:s01", "test:p02", "test:o01" },
+          { "test:s01", "test:p02", "test:o02" },
+          { "test:s01", "test:p03", "test:o02" },
+          { "test:s02", "test:p03", "test:o02" },
+          { "test:s02", "test:p04", "test:o02" },
+          { "test:s02", "test:p04", "test:o03" },
+          { "test:s02", "test:p05", "test:o03" },
+          { "test:s03", "test:p01", "test:o01" },
+          { "test:s03", "test:p05", "test:o03" },
+          { "test:s03", "test:p06", "test:o01" },
+          { "test:s03", "test:p06", "test:o03" },
+        };
+        compareResults(results, answer2);
+        answer2.close();
+
+        txManager.begin();
+        txManager.getTransaction().enlistResource(rwResource);
+        session.removeModel(model2URI);
+        txManager.commit();
+
+        txManager.resume(tx2);
+        txManager.commit();
+        txManager.resume(tx3);
+        txManager.commit();
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testSubqueryQuery() throws URISyntaxException {
+    logger.info("testSubqueryQuery");
+
+    try {
+      txManager.begin();
+      Session session = sessionFactory.newSession();
+      txManager.getTransaction().enlistResource(session.getReadOnlyXAResource());
+
+      try {
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(new Subquery(new Variable("k0"), new Query(
+          Collections.singletonList(objectVariable),
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(objectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        )));
+
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+              new URIReferenceImpl(new URI("test:p03")),
+              objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        Transaction tx1 = txManager.suspend();
+
+        answer.beforeFirst();
+
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:s01")),
+            answer.getObject(0));
+        Answer sub1 = (Answer)answer.getObject(1);
+        compareResults(new String[][] { new String[] { "test:o01" },
+                                        new String[] { "test:o02" } }, sub1);
+        sub1.close();
+
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:s02")),
+            answer.getObject(0));
+        Answer sub2 = (Answer)answer.getObject(1);
+        compareResults(new String[][] { new String[] { "test:o02" },
+                                        new String[] { "test:o03" } }, sub2);
+        // Leave sub2 open.
+
+        assertFalse(answer.next());
+        answer.close();
+        sub2.close();
+
+        // Leave transaction to be closed on session close.
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+/*
+
+  public void testConcurrentSubqueryQuery() throws URISyntaxException {
+    logger.info("testConcurrentSubqueryQuery");
+
+    try {
+      Session session = database.newSession();
+      XAResource rwResource = session.getXAResource();
+      Xid xid1 = new TestXid(1);
+      rwResource.start(xid1, XAResource.TMNOFLAGS);
+
+      try {
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(new Subquery(new Variable("k0"), new Query(
+          Collections.singletonList(objectVariable),
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(objectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        )));
+
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+              new URIReferenceImpl(new URI("test:p03")),
+              objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        answer.beforeFirst();
+
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:s01")),
+            answer.getObject(0));
+        Answer sub1 = (Answer)answer.getObject(1);
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:s02")),
+            answer.getObject(0));
+        Answer sub2 = (Answer)answer.getObject(1);
+        assertFalse(answer.next());
+
+        assertEquals(1, sub1.getNumberOfVariables());
+        assertEquals(1, sub2.getNumberOfVariables());
+        sub1.beforeFirst();
+        sub2.beforeFirst();
+        assertTrue(sub1.next());
+        assertTrue(sub2.next());
+        assertEquals(new URIReferenceImpl(new URI("test:o01")), sub1.getObject(0));
+        assertEquals(new URIReferenceImpl(new URI("test:o02")), sub2.getObject(0));
+
+        rwResource.end(xid1, XAResource.TMSUSPEND);
+
+        assertTrue(sub1.next());
+        assertTrue(sub2.next());
+        assertEquals(new URIReferenceImpl(new URI("test:o02")), sub1.getObject(0));
+        assertEquals(new URIReferenceImpl(new URI("test:o03")), sub2.getObject(0));
+        assertFalse(sub1.next());
+        assertFalse(sub2.next());
+
+        answer.close();
+
+        rwResource.end(xid1, XAResource.TMSUCCESS);
+        rwResource.commit(xid1, true);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testExplicitIsolationQuerySingleSession() throws URISyntaxException
+  {
+    logger.info("testExplicitIsolationQuery");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session = database.newSession();
+      try {
+        XAResource roResource = session.getReadOnlyXAResource();
+        XAResource rwResource = session.getXAResource();
+        Xid xid1 = new TestXid(1); // Initial create model.
+        Xid xid2 = new TestXid(2); // Started before setModel.
+        Xid xid3 = new TestXid(3); // setModel.
+        Xid xid4 = new TestXid(4); // Started before setModel prepares
+        Xid xid5 = new TestXid(5); // Started before setModel commits
+        Xid xid6 = new TestXid(6); // Started after setModel commits
+        Xid xid7 = new TestXid(7); // Final remove model.
+
+        rwResource.start(xid1, XAResource.TMNOFLAGS);
+        session.createModel(model3URI, null);
+        rwResource.end(xid1, XAResource.TMSUCCESS);
+        rwResource.commit(xid1, true);
+
+        // Nothing visible.
+        roResource.start(xid2, XAResource.TMNOFLAGS);
+        assertChangeNotVisible(session);
+        roResource.end(xid2, XAResource.TMSUSPEND);
+
+        // Perform update
+        rwResource.start(xid3, XAResource.TMNOFLAGS);
+        session.setModel(model3URI, new ModelResource(fileURI));
+        rwResource.end(xid3, XAResource.TMSUSPEND);
+
+        // Check uncommitted change not visible
+        roResource.start(xid4, XAResource.TMNOFLAGS);
+        assertChangeNotVisible(session);
+        roResource.end(xid4, XAResource.TMSUSPEND);
+
+        // Check original phase unaffected.
+        roResource.start(xid2, XAResource.TMRESUME);
+        assertChangeNotVisible(session);
+        roResource.end(xid2, XAResource.TMSUSPEND);
+
+        // Check micro-commit visible to current-phase
+        rwResource.start(xid3, XAResource.TMRESUME);
+        assertChangeVisible(session);
+        // Perform prepare
+        rwResource.end(xid3, XAResource.TMSUCCESS);
+        rwResource.prepare(xid3);
+
+        // Check original phase unaffected
+        roResource.start(xid2, XAResource.TMRESUME);
+        assertChangeNotVisible(session);
+        roResource.end(xid2, XAResource.TMSUSPEND);
+
+        // Check pre-prepare phase unaffected
+        roResource.start(xid4, XAResource.TMRESUME);
+        assertChangeNotVisible(session);
+        roResource.end(xid4, XAResource.TMSUSPEND);
+
+        // Check committed phase unaffected.
+        roResource.start(xid5, XAResource.TMNOFLAGS);
+        assertChangeNotVisible(session);
+        roResource.end(xid5, XAResource.TMSUSPEND);
+
+        // Do commit
+        rwResource.commit(xid3, false);
+
+        // Check original phase
+        roResource.start(xid2, XAResource.TMRESUME);
+        assertChangeNotVisible(session);
+        roResource.end(xid2, XAResource.TMSUSPEND);
+
+        // Check pre-prepare
+        roResource.start(xid4, XAResource.TMRESUME);
+        assertChangeNotVisible(session);
+        roResource.end(xid4, XAResource.TMSUSPEND);
+
+        // Check pre-commit
+        roResource.start(xid5, XAResource.TMRESUME);
+        assertChangeNotVisible(session);
+        roResource.end(xid5, XAResource.TMSUSPEND);
+
+        // Check committed phase is now updated
+        roResource.start(xid6, XAResource.TMNOFLAGS);
+        assertChangeVisible(session);
+
+        // Cleanup transactions.
+        roResource.end(xid6, XAResource.TMSUCCESS);
+        roResource.end(xid2, XAResource.TMSUCCESS);
+        roResource.end(xid4, XAResource.TMSUCCESS);
+        roResource.end(xid5, XAResource.TMSUCCESS);
+        roResource.commit(xid2, true);
+        roResource.commit(xid4, true);
+        roResource.commit(xid5, true);
+        roResource.commit(xid6, true);
+
+        // Cleanup database
+        rwResource.start(xid7, XAResource.TMNOFLAGS);
+        session.removeModel(model3URI);
+        rwResource.end(xid7, XAResource.TMSUCCESS);
+        rwResource.commit(xid7, true);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testExternalInternalIsolation() throws URISyntaxException
+  {
+    logger.info("testExplicitIsolationQuery");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        Session session2 = database.newSession();
+        try {
+          XAResource roResource = session1.getReadOnlyXAResource();
+          XAResource rwResource = session1.getXAResource();
+          Xid xid1 = new TestXid(1); // Initial create model.
+          Xid xid2 = new TestXid(2); // Main Test.
+          Xid xid3 = new TestXid(3); // Cleanup test.
+
+          rwResource.start(xid1, XAResource.TMNOFLAGS);
+          session1.createModel(model3URI, null);
+          rwResource.end(xid1, XAResource.TMSUCCESS);
+          rwResource.commit(xid1, true);
+
+          // Nothing visible.
+          assertChangeNotVisible(session2);
+
+          // Perform update
+          rwResource.start(xid2, XAResource.TMNOFLAGS);
+          session1.setModel(model3URI, new ModelResource(fileURI));
+          rwResource.end(xid2, XAResource.TMSUSPEND);
+
+          // Check uncommitted change not visible
+          assertChangeNotVisible(session2);
+
+          // Check micro-commit visible to current-phase
+          rwResource.start(xid2, XAResource.TMRESUME);
+          assertChangeVisible(session1);
+          // Perform prepare
+          rwResource.end(xid2, XAResource.TMSUCCESS);
+          rwResource.prepare(xid2);
+
+          // Check original phase unaffected
+          assertChangeNotVisible(session2);
+
+          // Do commit
+          rwResource.commit(xid2, false);
+
+          // Check committed phase is now updated
+          assertChangeVisible(session2);
+
+          // Cleanup database
+          session2.removeModel(model3URI);
+        } finally {
+          session2.close();
+        }
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  public void testInternalExternalIsolation() throws URISyntaxException
+  {
+    logger.info("testExplicitIsolationQuery");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        Session session2 = database.newSession();
+        try {
+          XAResource roResource = session2.getReadOnlyXAResource();
+          XAResource rwResource = session2.getXAResource();
+          Xid xid1 = new TestXid(1); // Pre-update
+          Xid xid2 = new TestXid(2); // Post-update/Pre-commit
+          Xid xid3 = new TestXid(3); // Post-commit
+
+          session1.createModel(model3URI, null);
+
+          // Nothing visible.
+          roResource.start(xid1, XAResource.TMNOFLAGS);
+          assertChangeNotVisible(session2);
+          roResource.end(xid1, XAResource.TMSUSPEND);
+
+          // Perform update with autocommit off
+          session1.setAutoCommit(false);
+          session1.setModel(model3URI, new ModelResource(fileURI));
+
+          // Check uncommitted change not visible
+          roResource.start(xid2, XAResource.TMNOFLAGS);
+          assertChangeNotVisible(session2);
+          roResource.end(xid2, XAResource.TMSUSPEND);
+
+          // Check original phase unaffected.
+          roResource.start(xid1, XAResource.TMRESUME);
+          assertChangeNotVisible(session2);
+          roResource.end(xid1, XAResource.TMSUSPEND);
+
+          // Check micro-commit visible to current-phase
+          assertChangeVisible(session1);
+          session1.setAutoCommit(true);
+
+          // Check original phase unaffected
+          roResource.start(xid1, XAResource.TMRESUME);
+          assertChangeNotVisible(session2);
+          roResource.end(xid1, XAResource.TMSUSPEND);
+
+          // Check pre-commit phase unaffected
+          roResource.start(xid2, XAResource.TMRESUME);
+          assertChangeNotVisible(session2);
+          roResource.end(xid2, XAResource.TMSUSPEND);
+
+          // Check committed phase is now updated and write-lock available
+          rwResource.start(xid3, XAResource.TMNOFLAGS);
+          assertChangeVisible(session2);
+          
+          // Check internal transaction read-only
+          assertChangeVisible(session1);
+
+          // Cleanup transactions.
+          rwResource.end(xid3, XAResource.TMSUCCESS);
+          roResource.end(xid2, XAResource.TMSUCCESS);
+          roResource.end(xid1, XAResource.TMSUCCESS);
+          roResource.commit(xid1, true);
+          roResource.commit(xid2, true);
+          rwResource.commit(xid3, true);
+
+          // Cleanup database (check write-lock available again)
+          session1.removeModel(model3URI);
+        } finally {
+          session2.close();
+        }
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  private void assertChangeVisible(Session session) throws Exception {
+    Variable subjectVariable   = new Variable("subject");
+    Variable predicateVariable = new Variable("predicate");
+    Variable objectVariable    = new Variable("object");
+
+    List selectList = new ArrayList(3);
+    selectList.add(subjectVariable);
+    selectList.add(predicateVariable);
+    selectList.add(objectVariable);
+
+    // Evaluate the query
+    Answer answer = session.query(new Query(
+      selectList,                                       // SELECT
+      new ModelResource(model3URI),                      // FROM
+      new ConstraintImpl(subjectVariable,               // WHERE
+                     predicateVariable,
+                     objectVariable),
+      null,                                             // HAVING
+      Arrays.asList(new Order[] {                       // ORDER BY
+        new Order(subjectVariable, true),
+        new Order(predicateVariable, true),
+        new Order(objectVariable, true)
+      }),
+      null,                                             // LIMIT
+      0,                                                // OFFSET
+      new UnconstrainedAnswer()                         // GIVEN
+    ));
+
+    String[][] results = {
+      { "test:s01", "test:p01", "test:o01" },
+      { "test:s01", "test:p02", "test:o01" },
+      { "test:s01", "test:p02", "test:o02" },
+      { "test:s01", "test:p03", "test:o02" },
+      { "test:s02", "test:p03", "test:o02" },
+      { "test:s02", "test:p04", "test:o02" },
+      { "test:s02", "test:p04", "test:o03" },
+      { "test:s02", "test:p05", "test:o03" },
+      { "test:s03", "test:p01", "test:o01" },
+      { "test:s03", "test:p05", "test:o03" },
+      { "test:s03", "test:p06", "test:o01" },
+      { "test:s03", "test:p06", "test:o03" },
+    };
+    compareResults(results, answer);
+    answer.close();
+  }
+
+  private void assertChangeNotVisible(Session session) throws Exception {
+    Variable subjectVariable   = new Variable("subject");
+    Variable predicateVariable = new Variable("predicate");
+    Variable objectVariable    = new Variable("object");
+
+    List selectList = new ArrayList(3);
+    selectList.add(subjectVariable);
+    selectList.add(predicateVariable);
+    selectList.add(objectVariable);
+
+    // Evaluate the query
+    Answer answer = session.query(new Query(
+      selectList,                                       // SELECT
+      new ModelResource(model3URI),                      // FROM
+      new ConstraintImpl(subjectVariable,               // WHERE
+                     predicateVariable,
+                     objectVariable),
+      null,                                             // HAVING
+      Arrays.asList(new Order[] {                       // ORDER BY
+        new Order(subjectVariable, true),
+        new Order(predicateVariable, true),
+        new Order(objectVariable, true)
+      }),
+      null,                                             // LIMIT
+      0,                                                // OFFSET
+      new UnconstrainedAnswer()                         // GIVEN
+    ));
+    answer.beforeFirst();
+    assertFalse(answer.next());
+    answer.close();
+  }
+
+  //
+  // Test two simultaneous, explicit transactions, in two threads. The second one should block
+  // until the first one sets auto-commit back to true.
+  //
+  public void testConcurrentExplicitTxn() throws URISyntaxException
+  {
+    logger.info("testConcurrentExplicitTxn");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        XAResource resource1 = session1.getXAResource();
+        resource1.start(new TestXid(1), XAResource.TMNOFLAGS);
+        session1.createModel(model3URI, null);
+        resource1.end(new TestXid(1), XAResource.TMSUCCESS);
+        resource1.commit(new TestXid(1), true);
+
+        resource1.start(new TestXid(2), XAResource.TMNOFLAGS);
+        session1.setModel(model3URI, new ModelResource(fileURI));
+
+        final boolean[] tx2Started = new boolean[] { false };
+
+        Thread t2 = new Thread("tx2Test") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              XAResource resource2 = session2.getXAResource();
+              try {
+                resource2.start(new TestXid(3), XAResource.TMNOFLAGS);
+
+                synchronized (tx2Started) {
+                  tx2Started[0] = true;
+                  tx2Started.notify();
+                }
+
+                Variable subjectVariable   = new Variable("subject");
+                Variable predicateVariable = new Variable("predicate");
+                Variable objectVariable    = new Variable("object");
+
+                List selectList = new ArrayList(3);
+                selectList.add(subjectVariable);
+                selectList.add(predicateVariable);
+                selectList.add(objectVariable);
+
+                // Evaluate the query
+                Answer answer = session2.query(new Query(
+                  selectList,                                       // SELECT
+                  new ModelResource(model3URI),                      // FROM
+                  new ConstraintImpl(subjectVariable,               // WHERE
+                                 predicateVariable,
+                                 objectVariable),
+                  null,                                             // HAVING
+                  Arrays.asList(new Order[] {                       // ORDER BY
+                    new Order(subjectVariable, true),
+                    new Order(predicateVariable, true),
+                    new Order(objectVariable, true)
+                  }),
+                  null,                                             // LIMIT
+                  0,                                                // OFFSET
+                  new UnconstrainedAnswer()                         // GIVEN
+                ));
+
+                String[][] results = {
+                  { "test:s01", "test:p01", "test:o01" },
+                  { "test:s01", "test:p02", "test:o01" },
+                  { "test:s01", "test:p02", "test:o02" },
+                  { "test:s01", "test:p03", "test:o02" },
+                  { "test:s02", "test:p03", "test:o02" },
+                  { "test:s02", "test:p04", "test:o02" },
+                  { "test:s02", "test:p04", "test:o03" },
+                  { "test:s02", "test:p05", "test:o03" },
+                  { "test:s03", "test:p01", "test:o01" },
+                  { "test:s03", "test:p05", "test:o03" },
+                  { "test:s03", "test:p06", "test:o01" },
+                  { "test:s03", "test:p06", "test:o03" },
+                };
+                compareResults(results, answer);
+                answer.close();
+
+                resource2.end(new TestXid(3), XAResource.TMSUCCESS);
+                resource2.commit(new TestXid(3), true);
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        resource1.commit(new TestXid(2), true);
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+            assertTrue("second transaction should've started", tx2Started[0]);
+          }
+        }
+
+        try {
+          t2.join(2000L);
+        } catch (InterruptedException ie) {
+          logger.error("wait for tx2-terminated interrupted", ie);
+          fail(ie);
+        }
+        assertFalse("second transaction should've terminated", t2.isAlive());
+
+        resource1.start(new TestXid(4), XAResource.TMNOFLAGS);
+        session1.removeModel(model3URI);
+        resource1.end(new TestXid(4), XAResource.TMSUCCESS);
+        resource1.commit(new TestXid(4), true);
+
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  //*
+  // Test two simultaneous transactions, in two threads. The second one should block
+  // until the first one sets auto-commit back to true.
+  ///
+  public void testExternalInternalConcurrentTxn() throws URISyntaxException
+  {
+    logger.info("testExternalInternalConcurrentTxn");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        XAResource resource1 = session1.getXAResource();
+        resource1.start(new TestXid(1), XAResource.TMNOFLAGS);
+        session1.createModel(model3URI, null);
+        resource1.end(new TestXid(1), XAResource.TMSUCCESS);
+        resource1.commit(new TestXid(1), true);
+
+        resource1.start(new TestXid(2), XAResource.TMNOFLAGS);
+        session1.setModel(model3URI, new ModelResource(fileURI));
+
+        final boolean[] tx2Started = new boolean[] { false };
+
+        Thread t2 = new Thread("tx2Test") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              try {
+                session2.setAutoCommit(false);
+
+                synchronized (tx2Started) {
+                  tx2Started[0] = true;
+                  tx2Started.notify();
+                }
+
+                Variable subjectVariable   = new Variable("subject");
+                Variable predicateVariable = new Variable("predicate");
+                Variable objectVariable    = new Variable("object");
+
+                List selectList = new ArrayList(3);
+                selectList.add(subjectVariable);
+                selectList.add(predicateVariable);
+                selectList.add(objectVariable);
+
+                // Evaluate the query
+                Answer answer = session2.query(new Query(
+                  selectList,                                       // SELECT
+                  new ModelResource(model3URI),                      // FROM
+                  new ConstraintImpl(subjectVariable,               // WHERE
+                                 predicateVariable,
+                                 objectVariable),
+                  null,                                             // HAVING
+                  Arrays.asList(new Order[] {                       // ORDER BY
+                    new Order(subjectVariable, true),
+                    new Order(predicateVariable, true),
+                    new Order(objectVariable, true)
+                  }),
+                  null,                                             // LIMIT
+                  0,                                                // OFFSET
+                  new UnconstrainedAnswer()                         // GIVEN
+                ));
+
+                String[][] results = {
+                  { "test:s01", "test:p01", "test:o01" },
+                  { "test:s01", "test:p02", "test:o01" },
+                  { "test:s01", "test:p02", "test:o02" },
+                  { "test:s01", "test:p03", "test:o02" },
+                  { "test:s02", "test:p03", "test:o02" },
+                  { "test:s02", "test:p04", "test:o02" },
+                  { "test:s02", "test:p04", "test:o03" },
+                  { "test:s02", "test:p05", "test:o03" },
+                  { "test:s03", "test:p01", "test:o01" },
+                  { "test:s03", "test:p05", "test:o03" },
+                  { "test:s03", "test:p06", "test:o01" },
+                  { "test:s03", "test:p06", "test:o03" },
+                };
+                compareResults(results, answer);
+                answer.close();
+
+                session2.setAutoCommit(true);
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        resource1.commit(new TestXid(2), true);
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+            assertTrue("second transaction should've started", tx2Started[0]);
+          }
+        }
+
+        try {
+          t2.join(2000L);
+        } catch (InterruptedException ie) {
+          logger.error("wait for tx2-terminated interrupted", ie);
+          fail(ie);
+        }
+        assertFalse("second transaction should've terminated", t2.isAlive());
+
+        resource1.start(new TestXid(4), XAResource.TMNOFLAGS);
+        session1.removeModel(model3URI);
+        resource1.end(new TestXid(4), XAResource.TMSUCCESS);
+        resource1.commit(new TestXid(4), true);
+
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  //*
+  // Test two simultaneous transactions, in two threads. The second one should block
+  // until the first one sets auto-commit back to true.
+  ///
+  public void testInternalExternalConcurrentTxn() throws URISyntaxException
+  {
+    logger.info("testInternalExternalConcurrentTxn");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        session1.createModel(model3URI, null);
+
+        session1.setAutoCommit(false);
+        session1.setModel(model3URI, new ModelResource(fileURI));
+
+        final boolean[] tx2Started = new boolean[] { false };
+
+        Thread t2 = new Thread("tx2Test") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              try {
+                XAResource resource = session2.getXAResource();
+                resource.start(new TestXid(1), XAResource.TMNOFLAGS);
+
+                synchronized (tx2Started) {
+                  tx2Started[0] = true;
+                  tx2Started.notify();
+                }
+
+                Variable subjectVariable   = new Variable("subject");
+                Variable predicateVariable = new Variable("predicate");
+                Variable objectVariable    = new Variable("object");
+
+                List selectList = new ArrayList(3);
+                selectList.add(subjectVariable);
+                selectList.add(predicateVariable);
+                selectList.add(objectVariable);
+
+                // Evaluate the query
+                Answer answer = session2.query(new Query(
+                  selectList,                                       // SELECT
+                  new ModelResource(model3URI),                      // FROM
+                  new ConstraintImpl(subjectVariable,               // WHERE
+                                 predicateVariable,
+                                 objectVariable),
+                  null,                                             // HAVING
+                  Arrays.asList(new Order[] {                       // ORDER BY
+                    new Order(subjectVariable, true),
+                    new Order(predicateVariable, true),
+                    new Order(objectVariable, true)
+                  }),
+                  null,                                             // LIMIT
+                  0,                                                // OFFSET
+                  new UnconstrainedAnswer()                         // GIVEN
+                ));
+
+                String[][] results = {
+                  { "test:s01", "test:p01", "test:o01" },
+                  { "test:s01", "test:p02", "test:o01" },
+                  { "test:s01", "test:p02", "test:o02" },
+                  { "test:s01", "test:p03", "test:o02" },
+                  { "test:s02", "test:p03", "test:o02" },
+                  { "test:s02", "test:p04", "test:o02" },
+                  { "test:s02", "test:p04", "test:o03" },
+                  { "test:s02", "test:p05", "test:o03" },
+                  { "test:s03", "test:p01", "test:o01" },
+                  { "test:s03", "test:p05", "test:o03" },
+                  { "test:s03", "test:p06", "test:o01" },
+                  { "test:s03", "test:p06", "test:o03" },
+                };
+                compareResults(results, answer);
+                answer.close();
+
+                resource.end(new TestXid(1), XAResource.TMSUCCESS);
+                resource.rollback(new TestXid(1));
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        session1.commit();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        session1.setAutoCommit(true);
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+            assertTrue("second transaction should've started", tx2Started[0]);
+          }
+        }
+
+        try {
+          t2.join(2000L);
+        } catch (InterruptedException ie) {
+          logger.error("wait for tx2-terminated interrupted", ie);
+          fail(ie);
+        }
+        assertFalse("second transaction should've terminated", t2.isAlive());
+
+        session1.removeModel(model3URI);
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  //*
+  // Test two simultaneous transactions, in two threads. The second one should block
+  // until the first one sets auto-commit back to true.
+  ///
+  public void testExternalInternalConcurrentTxnRollback() throws URISyntaxException
+  {
+    logger.info("testExternalInternalConcurrentTxnRollback");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        XAResource resource1 = session1.getXAResource();
+        resource1.start(new TestXid(1), XAResource.TMNOFLAGS);
+        session1.createModel(model3URI, null);
+        resource1.end(new TestXid(1), XAResource.TMSUCCESS);
+        resource1.commit(new TestXid(1), true);
+
+        resource1.start(new TestXid(2), XAResource.TMNOFLAGS);
+        session1.setModel(model3URI, new ModelResource(fileURI));
+
+        final boolean[] tx2Started = new boolean[] { false };
+
+        Thread t2 = new Thread("tx2Test") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              try {
+                session2.setAutoCommit(false);
+
+                synchronized (tx2Started) {
+                  tx2Started[0] = true;
+                  tx2Started.notify();
+                }
+
+                Variable subjectVariable   = new Variable("subject");
+                Variable predicateVariable = new Variable("predicate");
+                Variable objectVariable    = new Variable("object");
+
+                List selectList = new ArrayList(3);
+                selectList.add(subjectVariable);
+                selectList.add(predicateVariable);
+                selectList.add(objectVariable);
+
+                // Evaluate the query
+                Answer answer = session2.query(new Query(
+                  selectList,                                       // SELECT
+                  new ModelResource(model3URI),                      // FROM
+                  new ConstraintImpl(subjectVariable,               // WHERE
+                                 predicateVariable,
+                                 objectVariable),
+                  null,                                             // HAVING
+                  Arrays.asList(new Order[] {                       // ORDER BY
+                    new Order(subjectVariable, true),
+                    new Order(predicateVariable, true),
+                    new Order(objectVariable, true)
+                  }),
+                  null,                                             // LIMIT
+                  0,                                                // OFFSET
+                  new UnconstrainedAnswer()                         // GIVEN
+                ));
+
+                answer.beforeFirst();
+                assertFalse(answer.next());
+                answer.close();
+
+                session2.setAutoCommit(true);
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        resource1.rollback(new TestXid(2));
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+            assertTrue("second transaction should've started", tx2Started[0]);
+          }
+        }
+
+        try {
+          t2.join(2000L);
+        } catch (InterruptedException ie) {
+          logger.error("wait for tx2-terminated interrupted", ie);
+          fail(ie);
+        }
+        assertFalse("second transaction should've terminated", t2.isAlive());
+
+        resource1.start(new TestXid(4), XAResource.TMNOFLAGS);
+        session1.removeModel(model3URI);
+        resource1.end(new TestXid(4), XAResource.TMSUCCESS);
+        resource1.commit(new TestXid(4), true);
+
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  //*
+  // Test two simultaneous transactions, in two threads. The second one should block
+  // until the first one sets auto-commit back to true.
+  ///
+  public void testInternalExternalConcurrentTxnRollback() throws URISyntaxException
+  {
+    logger.info("testInternalExternalConcurrentTxnRollback");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        session1.createModel(model3URI, null);
+
+        session1.setAutoCommit(false);
+        session1.setModel(model3URI, new ModelResource(fileURI));
+
+        final boolean[] tx2Started = new boolean[] { false };
+
+        Thread t2 = new Thread("tx2Test") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              try {
+                XAResource resource = session2.getXAResource();
+                resource.start(new TestXid(1), XAResource.TMNOFLAGS);
+
+                synchronized (tx2Started) {
+                  tx2Started[0] = true;
+                  tx2Started.notify();
+                }
+
+                Variable subjectVariable   = new Variable("subject");
+                Variable predicateVariable = new Variable("predicate");
+                Variable objectVariable    = new Variable("object");
+
+                List selectList = new ArrayList(3);
+                selectList.add(subjectVariable);
+                selectList.add(predicateVariable);
+                selectList.add(objectVariable);
+
+                // Evaluate the query
+                Answer answer = session2.query(new Query(
+                  selectList,                                       // SELECT
+                  new ModelResource(model3URI),                      // FROM
+                  new ConstraintImpl(subjectVariable,               // WHERE
+                                 predicateVariable,
+                                 objectVariable),
+                  null,                                             // HAVING
+                  Arrays.asList(new Order[] {                       // ORDER BY
+                    new Order(subjectVariable, true),
+                    new Order(predicateVariable, true),
+                    new Order(objectVariable, true)
+                  }),
+                  null,                                             // LIMIT
+                  0,                                                // OFFSET
+                  new UnconstrainedAnswer()                         // GIVEN
+                ));
+
+                answer.beforeFirst();
+                assertFalse(answer.next());
+                answer.close();
+
+                resource.end(new TestXid(1), XAResource.TMFAIL);
+                resource.rollback(new TestXid(1));
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        session1.rollback();
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+          }
+          assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+        }
+
+        session1.setAutoCommit(true);
+
+        synchronized (tx2Started) {
+          if (!tx2Started[0]) {
+            try {
+              tx2Started.wait(2000L);
+            } catch (InterruptedException ie) {
+              logger.error("wait for tx2-started interrupted", ie);
+              fail(ie);
+            }
+            assertTrue("second transaction should've started", tx2Started[0]);
+          }
+        }
+
+        try {
+          t2.join(2000L);
+        } catch (InterruptedException ie) {
+          logger.error("wait for tx2-terminated interrupted", ie);
+          fail(ie);
+        }
+        assertFalse("second transaction should've terminated", t2.isAlive());
+
+        session1.removeModel(model3URI);
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  public void testExplicitRollbackIsolationQuery() throws URISyntaxException {
+    logger.info("testExplicitRollbackIsolationQuery");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session = database.newSession();
+      XAResource roResource = session.getReadOnlyXAResource();
+      XAResource rwResource = session.getXAResource();
+      try {
+        rwResource.start(new TestXid(1), XAResource.TMNOFLAGS);
+        session.createModel(model3URI, null);
+        rwResource.end(new TestXid(1), XAResource.TMSUCCESS);
+        rwResource.commit(new TestXid(1), true);
+
+        rwResource.start(new TestXid(2), XAResource.TMNOFLAGS);
+        session.setModel(model3URI, new ModelResource(fileURI));
+        rwResource.end(new TestXid(2), XAResource.TMSUSPEND);
+
+        roResource.start(new TestXid(3), XAResource.TMNOFLAGS);
+
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(model3URI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+        answer.beforeFirst();
+        assertFalse(answer.next());
+        answer.close();
+
+        roResource.end(new TestXid(3), XAResource.TMSUCCESS);
+        roResource.commit(new TestXid(3), true);
+
+        rwResource.end(new TestXid(2), XAResource.TMFAIL);
+        rwResource.rollback(new TestXid(2));
+
+        roResource.start(new TestXid(4), XAResource.TMNOFLAGS);
+        selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(model3URI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        answer.beforeFirst();
+        assertFalse(answer.next());
+        answer.close();
+
+        roResource.end(new TestXid(4), XAResource.TMFAIL);
+        roResource.rollback(new TestXid(4));
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+*/
+
+  public void testTrivalImplicit() throws URISyntaxException
+  {
+    logger.info("testTrivialImplicit");
+    try {
+      Session session = sessionFactory.newSession();
+
+      try {
+        session.removeModel(modelURI);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  //
+  // Internal methods
+  //
+
+  private void compareResults(String[][] expected, Answer answer) throws Exception {
+    try {
+      answer.beforeFirst();
+      for (int i = 0; i < expected.length; i++) {
+        assertTrue("Answer short at row " + i, answer.next());
+        assertEquals(expected[i].length, answer.getNumberOfVariables());
+        for (int j = 0; j < expected[i].length; j++) {
+          URIReferenceImpl uri = new URIReferenceImpl(new URI(expected[i][j]));
+          assertEquals(uri, answer.getObject(j));
+        }
+      }
+      assertFalse(answer.next());
+    } catch (Exception e) {
+      logger.info("Failed test - " + answer);
+      answer.close();
+      throw e;
+    }
+  }
+
+  private void compareResults(Answer answer1, Answer answer2) throws Exception {
+    answer1.beforeFirst();
+    answer2.beforeFirst();
+    assertEquals(answer1.getNumberOfVariables(), answer2.getNumberOfVariables());
+    while (answer1.next()) {
+      assertTrue(answer2.next());
+      for (int i = 0; i < answer1.getNumberOfVariables(); i++) {
+        assertEquals(answer1.getObject(i), answer2.getObject(i));
+      }
+    }
+    assertFalse(answer2.next());
+  }
+
+  /**
+   * Fail with an unexpected exception
+   */
+  private void fail(Throwable throwable)
+  {
+    StringWriter stringWriter = new StringWriter();
+    throwable.printStackTrace(new PrintWriter(stringWriter));
+    fail(stringWriter.toString());
+  }
+
+  private static class DummyXAResource implements XAResource {
+    public void end(Xid xid, int flags) throws XAException {}
+    public void forget(Xid xid) throws XAException {}
+    public int getTransactionTimeout() throws XAException { return 0; }
+    public int prepare(Xid xid) throws XAException { return 0; }
+    public Xid[] recover(int flag) throws XAException { return new Xid[] {}; }
+    public void rollback(Xid xid) throws XAException {}
+    public boolean setTransactionTimeout(int seconds) throws XAException { return false; }
+    public void start(Xid xid, int flags) throws XAException {}
+    public void commit(Xid xid, boolean twophase) throws XAException {}
+    public boolean isSameRM(XAResource xa) { return xa == this; }
+  }
+}

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -191,7 +191,7 @@
       constraint = appendMulgaraIsConstraint(vars[2], toValue(object), constraint);
 
       Query query = new Query(
-          Arrays.asList((Object[])vars),   // variable list
+          Arrays.asList(vars),             // variable list
           new ModelResource(modelURI),     // model expression
           constraint,                      // constraint expr
           null,                            // no having

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -85,7 +85,7 @@
    * If this field is not <code>null</code>, {@link #query} will be
    * <code>null</code>
    */
-  private final Set<Triple> tripleSet;
+  private final Set<? extends Triple> tripleSet;
 
   /**
    * The query generating the statements whose occurence is to be modified.
@@ -112,7 +112,7 @@
    * @throws IllegalArgumentException if <var>modelURI</var> or
    *   <var>tripleSet</var> are <code>null</code>
    */
-  ModifyModelOperation(URI modelURI, Set<Triple> tripleSet, boolean insert)
+  ModifyModelOperation(URI modelURI, Set<? extends Triple> tripleSet, boolean insert)
   {
     // Validate "modelURI" parameter
     if (modelURI == null) {

Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java	                        (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,332 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com) under contract to 
+ * Topaz Foundation. Portions created under this contract are
+ * Copyright (c) 2007 Topaz Foundation
+ * All Rights Reserved.
+ */
+package org.mulgara.resolver;
+
+// Java 2 enterprise packages
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import javax.transaction.xa.XAException;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+// Local packages
+import org.mulgara.resolver.spi.DatabaseMetadata;
+import org.mulgara.resolver.spi.EnlistableResource;
+import org.mulgara.resolver.spi.ResolverSessionFactory;
+
+import org.mulgara.query.MulgaraTransactionException;
+import org.mulgara.query.TuplesException;
+import org.mulgara.query.QueryException;
+
+/**
+ * @created 2007-11-06
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <a href="mailto:mail at netymon.com">Netymon Pty Ltd</a>
+ *
+ * @copyright &copy;2007 <a href="http://www.topazproject.org/">Topaz Foundation</a>
+ *
+ * @licence Open Software License v3.0
+ */
+public class MulgaraExternalTransaction implements MulgaraTransaction {
+  private static final Logger logger =
+    Logger.getLogger(MulgaraExternalTransaction.class.getName());
+
+  private Xid xid;
+
+  private Set<EnlistableResource> enlisted;
+  private Set<EnlistableResource> prepared;
+  private Set<EnlistableResource> committed;
+  private Set<EnlistableResource> rollbacked;
+
+  private Map<EnlistableResource, XAResource> xaResources;
+
+  private MulgaraExternalTransactionFactory factory;
+  private DatabaseOperationContext context;
+
+  private boolean hRollback;
+  private int heurCode;
+  private boolean rollback;
+
+  MulgaraExternalTransaction(MulgaraExternalTransactionFactory factory, Xid xid, DatabaseOperationContext context)
+      throws QueryException {
+    this.factory = factory;
+    this.context = context;
+    this.xid = xid;
+
+    this.enlisted = new HashSet<EnlistableResource>();
+    this.prepared = new HashSet<EnlistableResource>();
+    this.committed = new HashSet<EnlistableResource>();
+    this.rollbacked = new HashSet<EnlistableResource>();
+
+    this.xaResources = new HashMap<EnlistableResource, XAResource>();
+
+    this.hRollback = false;
+    this.heurCode = 0;
+    this.rollback = false;
+
+    this.context.initiate(this);
+  }
+
+  // We ignore reference counting in external transactions
+  public void reference() throws MulgaraTransactionException {}  
+  public void dereference() throws MulgaraTransactionException {}
+
+  public MulgaraTransactionException abortTransaction(String errorMessage, Throwable cause)
+      throws MulgaraTransactionException {
+    report("abortTransaction");
+    try {
+      for (EnlistableResource resource : enlisted) {
+        try {
+          resource.abort();
+        } catch (Throwable throw_away) {}
+      }
+      for (EnlistableResource resource : prepared) {
+        try {
+          resource.abort();
+        } catch (Throwable throw_away) {}
+      }
+
+      return new MulgaraTransactionException(errorMessage, cause);
+    } finally {
+      factory.transactionComplete(this);
+    }
+  }
+
+  public void heuristicRollback(String cause) throws MulgaraTransactionException {
+    report("heuristicRollback");
+    hRollback = true;
+    try {
+      rollback(xid);
+    } catch (XAException xa) {
+      throw new MulgaraTransactionException("Failed heuristic rollback", xa);
+    } finally {
+      heurCode = heurCode == 0 ? XAException.XA_HEURRB : heurCode;
+    }
+  }
+
+  public void execute(Operation operation,
+               ResolverSessionFactory resolverSessionFactory,
+               DatabaseMetadata metadata) throws MulgaraTransactionException {
+    // FIXME: Do I need to check that this transaction is 'active' ?
+    try {
+      operation.execute(context,
+                        context.getSystemResolver(),
+                        resolverSessionFactory,
+                        metadata);
+    } catch (Throwable th) {
+      try {
+        rollback(xid);
+      } catch (XAException ex) {
+        logger.error("Error in rollback after operation failure", ex);
+      }
+      throw new MulgaraTransactionException("Operation failed", th);
+    }
+  }
+
+  public AnswerOperationResult execute(AnswerOperation ao) throws TuplesException {
+    try {
+      ao.execute();
+      return ao.getResult();
+    } catch (Throwable th) {
+      try {
+        logger.warn("Error in answer operation triggered rollback", th);
+        rollback(xid);
+      } catch (XAException ex) {
+        logger.error("Error in rollback after answer-operation failure", ex);
+      }
+      throw new TuplesException("Request failed", th);
+    }
+  }
+
+  // FIXME: See if we can't rearrange things to allow this to be deleted.
+  public void execute(TransactionOperation to) throws MulgaraTransactionException {
+    to.execute();
+  }
+
+  public void enlist(EnlistableResource enlistable) throws MulgaraTransactionException {
+    try {
+      XAResource res = enlistable.getXAResource();
+      for (EnlistableResource eres : enlisted) {
+        if (res.isSameRM(xaResources.get(eres))) {
+          return;
+        }
+      }
+      enlisted.add(enlistable);
+      xaResources.put(enlistable, res);
+      // FIXME: We need to handle this uptodate operation properly - handle
+      // suspension or mid-prepare/commit.
+      // bringUptodate(res);
+      res.start(xid, XAResource.TMNOFLAGS);
+    } catch (XAException ex) {
+      throw new MulgaraTransactionException("Failed to enlist resource", ex);
+    }
+  }
+
+  //
+  // Methods used to manage transaction from XAResource.
+  //
+
+  void commit(Xid xid) throws XAException {
+    report("commit");
+    // FIXME: Consider the possiblity prepare failed, or was incomplete.
+    for (EnlistableResource er : prepared) {
+      xaResources.get(er).commit(xid, false);
+      committed.add(er);
+    }
+    cleanupTransaction();
+  }
+
+  boolean isHeuristicallyRollbacked() {
+    return hRollback;
+  }
+
+  boolean isHeuristicallyCommitted() {
+    return false;
+  }
+
+  int getHeuristicCode() {
+    return heurCode;
+  }
+
+  boolean isRollbacked() {
+    return rollback;
+  }
+
+  void prepare(Xid xid) throws XAException {
+    report("prepare");
+    for (EnlistableResource er : enlisted) {
+      xaResources.get(er).prepare(xid);
+      prepared.add(er);
+    }
+    // status = PREPARED; ?
+  }
+
+  /**
+   * Perform rollback.  Only throws exception if transaction is subject to
+   * Heuristic Completion.
+   */
+  void rollback(Xid xid) throws XAException {
+    report("rollback");
+    try {
+      rollback = true;
+      Map<EnlistableResource, XAException> rollbackFailed = new HashMap<EnlistableResource, XAException>();
+
+      for (EnlistableResource er : enlisted) {
+        try {
+          if (!committed.contains(er)) {
+            xaResources.get(er).rollback(xid);
+            rollbacked.add(er);
+          }
+        } catch (XAException ex) {
+          logger.error("Attempt to rollback resource failed", ex);
+          rollbackFailed.put(er, ex);
+        }
+      }
+
+      if (rollbackFailed.isEmpty()) {
+        if (committed.isEmpty()) {        // Clean failure and rollback
+          return; // SUCCESSFUL ROLLBACK - RETURN
+        } else {                          // No rollback-failure, but partial commit
+          heurCode = XAException.XA_HEURMIX;
+          throw new XAException(heurCode);
+        }
+      } else {
+        // Something went wrong - start by assuming if one committed all committed
+        heurCode = (committed.isEmpty()) ? 0 : XAException.XA_HEURCOM;
+        // Then check every rollback failure code for a contradiction to all committed.
+        for (XAException xaex : rollbackFailed.values()) {
+          switch (xaex.errorCode) {
+            case XAException.XA_HEURHAZ:  
+            case XAException.XAER_NOTA:
+            case XAException.XAER_RMERR:
+            case XAException.XAER_RMFAIL:
+            case XAException.XAER_INVAL:
+            case XAException.XAER_PROTO:
+              // All these amount to not knowing the result - so we have a hazard
+              // unless we already know we have a mixed result.
+              if (heurCode != XAException.XA_HEURMIX) {
+                heurCode = XAException.XA_HEURHAZ;
+              }
+              break;
+            case XAException.XA_HEURCOM:
+              if (!rollbacked.isEmpty() || heurCode == XAException.XA_HEURRB) {
+                // We know something else was rollbacked, so we know we have a mixed result.
+                heurCode = XAException.XA_HEURMIX;
+              } else if (heurCode == 0) {
+                heurCode = XAException.XA_HEURCOM;
+              } // else it's a HEURHAZ or a HEURCOM and stays that way.
+              break;
+            case XAException.XA_HEURRB:
+              if (!committed.isEmpty() || heurCode == XAException.XA_HEURCOM) {
+                heurCode = XAException.XA_HEURMIX;
+              } else if (heurCode == 0) {
+                heurCode = XAException.XA_HEURRB;
+              } // else it's a HEURHAZ or a HEURRB and stays that way.
+              break;
+            case XAException.XA_HEURMIX:
+              // It can't get worse than, we know we have a mixed result.
+              heurCode = XAException.XA_HEURMIX;
+              break;
+            default:
+              // The codes above are the only codes permitted from a rollback() so
+              // anything else indicates a serious error in the resource-manager.
+              throw new XAException(XAException.XAER_RMERR);
+          }
+        }
+
+        throw new XAException(heurCode);
+      }
+    } finally {
+      cleanupTransaction();
+    }
+  }
+
+
+  Xid getXid() {
+    return xid;
+  }
+
+  private void cleanupTransaction() throws XAException {
+    report("cleanupTransaction");
+    try {
+      factory.transactionComplete(this);
+    } catch (MulgaraTransactionException em) {
+      try {
+        logger.error("Failed to cleanup transaction", em);
+        abortTransaction("Failure in cleanup", em);
+        throw new XAException(XAException.XAER_RMERR);
+      } catch (MulgaraTransactionException em2) {
+        logger.error("Failed to abort transaction on cleanup failure", em2);
+        throw new XAException(XAException.XAER_RMFAIL);
+      }
+    }
+  }
+
+  private void report(String desc) {
+    if (logger.isInfoEnabled()) {
+      logger.info(desc + ": " + System.identityHashCode(this));
+    }
+  }
+}

Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransactionFactory.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransactionFactory.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransactionFactory.java	                        (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransactionFactory.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,243 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com) under contract to 
+ * Topaz Foundation. Portions created under this contract are
+ * Copyright (c) 2007 Topaz Foundation
+ * All Rights Reserved.
+ */
+
+package org.mulgara.resolver;
+
+// Java2 packages
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+// Local packages
+import org.mulgara.query.MulgaraTransactionException;
+import org.mulgara.query.QueryException;
+import org.mulgara.transaction.TransactionManagerFactory;
+import org.mulgara.util.Assoc1toNMap;
+
+/**
+ * Manages external transactions.
+ *
+ * @created 2007-11-06
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ *
+ * @copyright &copy;2007 <a href="http://www.topazproject.org/">Topaz Foundation</a>
+ *
+ * @licence Open Software License v3.0</a>
+ */
+
+public class MulgaraExternalTransactionFactory extends MulgaraTransactionFactory {
+  private Map<DatabaseSession, MulgaraExternalTransaction> associatedTransaction;
+  private Assoc1toNMap<DatabaseSession, MulgaraExternalTransaction> sessionXAMap;
+
+  private Map<DatabaseSession, MulgaraXAResourceContext> xaResources;
+
+  public MulgaraExternalTransactionFactory(MulgaraTransactionManager manager) {
+    super(manager);
+
+    this.associatedTransaction = new HashMap<DatabaseSession, MulgaraExternalTransaction>();
+    this.sessionXAMap = new Assoc1toNMap<DatabaseSession, MulgaraExternalTransaction>();
+    this.xaResources = new HashMap<DatabaseSession, MulgaraXAResourceContext>();
+  }
+
+  public MulgaraTransaction getTransaction(final DatabaseSession session, boolean write)
+      throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      MulgaraExternalTransaction xa = associatedTransaction.get(session);
+      if (xa == null) {
+        throw new MulgaraTransactionException("No externally mediated transaction associated with session");
+      } else if (write && xa != writeTransaction) {
+        throw new MulgaraTransactionException("RO-transaction associated with session when requesting write operation");
+      }
+
+      return xa;
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  protected MulgaraExternalTransaction createTransaction(final DatabaseSession session, Xid xid, boolean write)
+      throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      if (associatedTransaction.get(session) != null) {
+        throw new MulgaraTransactionException(
+            "Attempt to initiate transaction with existing transaction active with session");
+      }
+      if (write && manager.isHoldingWriteLock(session)) {
+        throw new MulgaraTransactionException("Attempt to initiate two write transactions from the same session");
+      }
+
+      if (write) {
+          runWithoutMutex(new TransactionOperation() {
+            public void execute() throws MulgaraTransactionException {
+              manager.obtainWriteLock(session);
+            }
+          });
+        try {
+          MulgaraExternalTransaction xa = new MulgaraExternalTransaction(this, xid, session.newOperationContext(true));
+          writeTransaction = xa;
+          associatedTransaction.put(session, xa);
+          sessionXAMap.put(session, xa);
+
+          return xa;
+        } catch (Throwable th) {
+          manager.releaseWriteLock(session);
+          throw new MulgaraTransactionException("Error initiating write transaction", th);
+        }
+      } else {
+        try {
+          MulgaraExternalTransaction xa = new MulgaraExternalTransaction(this, xid, session.newOperationContext(false));
+          associatedTransaction.put(session, xa);
+          sessionXAMap.put(session, xa);
+
+          return xa;
+        } catch (QueryException eq) {
+          throw new MulgaraTransactionException("Error obtaining new read-only operation-context", eq);
+        }
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public Set<MulgaraExternalTransaction> getTransactionsForSession(DatabaseSession session) {
+    acquireMutex();
+    try {
+      Set<MulgaraExternalTransaction> xas = sessionXAMap.getN(session);
+      return xas != null ? xas : Collections.<MulgaraExternalTransaction>emptySet();
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public XAResource getXAResource(DatabaseSession session, boolean writing) {
+    acquireMutex();
+    try {
+      MulgaraXAResourceContext xarc = xaResources.get(session);
+      if (xarc == null) {
+        xarc = new MulgaraXAResourceContext(this, session);
+        xaResources.put(session, xarc);
+      }
+
+      return xarc.getResource(writing);
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public void closingSession(DatabaseSession session) throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      try {
+        super.closingSession(session);
+      } finally {
+        xaResources.remove(session);
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public void transactionComplete(MulgaraExternalTransaction xa)
+      throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      if (xa == null) {
+        throw new IllegalArgumentException("Null transaction indicated completion");
+      }
+      DatabaseSession session = sessionXAMap.get1(xa);
+      if (xa == writeTransaction) {
+        manager.releaseWriteLock(session);
+        writeTransaction = null;
+      }
+      sessionXAMap.removeN(xa);
+      if (associatedTransaction.get(session) == xa) {
+        associatedTransaction.remove(session);
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public boolean hasAssociatedTransaction(DatabaseSession session) {
+    acquireMutex();
+    try {
+      return associatedTransaction.get(session) != null;
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public boolean associateTransaction(DatabaseSession session, MulgaraExternalTransaction xa) {
+    acquireMutex();
+    try {
+      if (associatedTransaction.get(session) != null) {
+        return false;
+      } else {
+        associatedTransaction.put(session, xa);
+        return true;
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public MulgaraExternalTransaction getAssociatedTransaction(DatabaseSession session) {
+    acquireMutex();
+    try {
+      return associatedTransaction.get(session);
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public void disassociateTransaction(DatabaseSession session, MulgaraExternalTransaction xa) 
+      throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      if (associatedTransaction.get(session) == xa) {
+        associatedTransaction.remove(session);
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  void abortWriteTransaction() throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      if (writeTransaction != null) {
+        writeTransaction.abortTransaction("Explicit abort requested by write-lock manager", new Throwable());
+        writeTransaction = null;
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+}

Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransaction.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransaction.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransaction.java	                        (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransaction.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,744 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com). Portions created
+ * by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd.
+ * All Rights Reserved.
+ *
+ * Work deriving from MulgaraTransaction Copyright (c) 2007 Topaz Foundation
+ * under contract by Andrae Muys (mailto:andrae at netymon.com).
+ */
+package org.mulgara.resolver;
+
+// Java 2 enterprise packages
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.xa.XAResource;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+// Local packages
+import org.mulgara.resolver.spi.DatabaseMetadata;
+import org.mulgara.resolver.spi.EnlistableResource;
+import org.mulgara.resolver.spi.ResolverSessionFactory;
+
+import org.mulgara.query.MulgaraTransactionException;
+import org.mulgara.query.TuplesException;
+import org.mulgara.query.QueryException;
+
+/**
+ * Responsible for the javax.transaction.Transaction object.
+ * Responsibilities
+ * Ensuring every begin or resume is followed by either a suspend or an end.
+ * Ensuring every suspend or end is preceeded by either a begin or a resume.
+ * In conjunction with TransactionalAnswer ensuring that
+ * all calls to operations on SubqueryAnswer are preceeded by a successful resume.
+ * all calls to operations on SubqueryAnswer conclude with a suspend as the last call prior to returning to the user.
+ * Collaborates with DatabaseTransactionManager to determine when to end the transaction.
+ *
+ * @created 2006-10-06
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <a href="mailto:mail at netymon.com">Netymon Pty Ltd</a>
+ *
+ * @copyright &copy;2006 <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
+ *
+ * @licence Open Software License v3.0
+ */
+public class MulgaraInternalTransaction implements MulgaraTransaction {
+  /**
+   * This is the state machine switch matching these states.
+      switch (state) {
+        case CONSTRUCTEDREF:
+        case CONSTRUCTEDUNREF:
+        case ACTUNREF:
+        case ACTREF:
+        case DEACTREF:
+        case FINISHED:
+        case FAILED:
+      }
+   */
+  private enum State { CONSTRUCTEDREF, CONSTRUCTEDUNREF, ACTUNREF, ACTREF, DEACTREF, FINISHED, FAILED };
+
+  /** Logger.  */
+  private static final Logger logger =
+    Logger.getLogger(MulgaraInternalTransaction.class.getName());
+
+  private MulgaraInternalTransactionFactory factory;
+  private DatabaseOperationContext context;
+  private Set<EnlistableResource> enlisted;
+
+  private Transaction transaction;
+  private Thread currentThread;
+
+  private ReentrantLock activationMutex;
+
+  private State state;
+  private int inuse;
+  private int using;
+
+  private Throwable rollbackCause;
+
+  public MulgaraInternalTransaction(MulgaraInternalTransactionFactory factory, DatabaseOperationContext context)
+      throws IllegalArgumentException {
+    report("Creating Transaction");
+
+    try {
+      if (factory == null) {
+        throw new IllegalArgumentException("Manager null in MulgaraTransaction");
+      } else if (context == null) {
+        throw new IllegalArgumentException("OperationContext null in MulgaraTransaction");
+      }
+      this.factory = factory;
+      this.context = context;
+      this.enlisted = new HashSet<EnlistableResource>();
+      this.activationMutex = new ReentrantLock();
+      this.currentThread = null;
+
+      inuse = 0;
+      using = 0;
+      state = State.CONSTRUCTEDUNREF;
+      rollbackCause = null;
+    } finally {
+      report("Finished Creating Transaction");
+    }
+  }
+
+
+  void activate() throws MulgaraTransactionException {
+//    report("Activating Transaction");
+    try {
+      synchronized (this) {
+        if (currentThread != null && !currentThread.equals(Thread.currentThread())) {
+          throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+        }
+      }
+      
+      acquireActivationMutex();
+      try {
+        switch (state) {
+          case CONSTRUCTEDUNREF:
+            startTransaction();
+            inuse = 1;
+            state = State.ACTUNREF;
+            try {
+              context.initiate(this);
+            } catch (Throwable th) {
+              throw implicitRollback(th);
+            }
+            break;
+          case CONSTRUCTEDREF:
+            startTransaction();
+            inuse = 1;
+            using = 1;
+            state = State.ACTREF;
+            try {
+              context.initiate(this);
+            } catch (Throwable th) {
+              throw implicitRollback(th);
+            }
+            break;
+          case DEACTREF:
+            resumeTransaction();
+            inuse = 1;
+            state = State.ACTREF;
+            break;
+          case ACTREF:
+          case ACTUNREF:
+            inuse++;
+            break;
+          case FINISHED:
+            throw new MulgaraTransactionException("Attempt to activate terminated transaction");
+          case FAILED:
+            throw new MulgaraTransactionException("Attempt to activate failed transaction", rollbackCause);
+        }
+      } finally {
+        releaseActivationMutex();
+      }
+
+      try {
+        checkActivated();
+      } catch (MulgaraTransactionException em) {
+        throw abortTransaction("Activate failed post-condition check", em);
+      }
+    } catch (MulgaraTransactionException em) {
+      throw em;
+    } catch (Throwable th) {
+      throw abortTransaction("Error activating transaction", th);
+    } finally {
+//      report("Leaving Activate transaction");
+    }
+  }
+
+
+  private void deactivate() throws MulgaraTransactionException {
+//    report("Deactivating transaction");
+
+    try {
+      synchronized (this) {
+        if (currentThread == null) {
+          throw new MulgaraTransactionException("Transaction not associated with thread");
+        } else if (!currentThread.equals(Thread.currentThread())) {
+          throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+        }
+      }
+      
+      acquireActivationMutex();
+      try {
+        switch (state) {
+          case ACTUNREF:
+            if (inuse == 1) {
+              commitTransaction();
+            }
+            inuse--;
+            break;
+          case ACTREF:
+            if (inuse == 1) {
+              suspendTransaction();
+            }
+            inuse--;
+            break;
+          case CONSTRUCTEDREF:
+            throw new MulgaraTransactionException("Attempt to deactivate uninitiated refed transaction");
+          case CONSTRUCTEDUNREF:
+            throw new MulgaraTransactionException("Attempt to deactivate uninitiated transaction");
+          case DEACTREF:
+            throw new IllegalStateException("Attempt to deactivate unactivated transaction");
+          case FINISHED:
+            if (inuse < 0) {
+              errorReport("Activation count failure - too many deacts - in finished transaction", null);
+            } else {
+              inuse--;
+            }
+            break;
+          case FAILED:
+            // Nothing to do here.
+            break;
+        }
+      } finally {
+        releaseActivationMutex();
+      }
+    } catch (MulgaraTransactionException em) {
+      throw em;
+    } catch (Throwable th) {
+      throw abortTransaction("Error deactivating transaction", th);
+    } finally {
+//      report("Leaving Deactivate Transaction");
+    }
+  }
+
+  // Note: The transaction is often not activated when these are called.
+  //       This occurs when setting autocommit, as this creates and
+  //       references a transaction object that won't be started/activated
+  //       until it is first used.
+  public void reference() throws MulgaraTransactionException {
+    try {
+      report("Referencing Transaction");
+
+      synchronized (this) {
+        if (currentThread != null && !currentThread.equals(Thread.currentThread())) {
+          throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+        }
+      }
+
+      switch (state) {
+        case CONSTRUCTEDUNREF:
+          state = State.CONSTRUCTEDREF;
+          break;
+        case ACTREF:
+        case ACTUNREF:
+          using++;
+          state = State.ACTREF;
+          break;
+        case DEACTREF:
+          using++;
+          break;
+        case CONSTRUCTEDREF:
+          throw new MulgaraTransactionException("Attempt to reference uninitated transaction twice");
+        case FINISHED:
+          throw new MulgaraTransactionException("Attempt to reference terminated transaction");
+        case FAILED:
+          throw new MulgaraTransactionException("Attempt to reference failed transaction", rollbackCause);
+      }
+    } catch (MulgaraTransactionException em) {
+      throw em;
+    } catch (Throwable th) {
+      report("Error referencing transaction");
+      throw implicitRollback(th);
+    } finally {
+      report("Leaving Reference Transaction");
+    }
+  }
+
+  public void dereference() throws MulgaraTransactionException {
+    report("Dereferencing Transaction");
+    try {
+      synchronized (this) {
+        if (currentThread != null && !currentThread.equals(Thread.currentThread())) {
+          throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+        }
+      }
+
+      switch (state) {
+        case ACTREF:
+          if (using == 1) {
+            state = State.ACTUNREF;
+          }
+          using--;
+          break;
+        case CONSTRUCTEDREF:
+          state = State.CONSTRUCTEDUNREF;
+          break;
+        case FINISHED:
+        case FAILED:
+          if (using < 1) {
+            errorReport("Reference count failure - too many derefs - in finished transaction", null);
+          } else {
+            using--;
+          }
+          break;
+        case ACTUNREF:
+          throw new IllegalStateException("Attempt to dereference unreferenced transaction");
+        case CONSTRUCTEDUNREF:
+          throw new MulgaraTransactionException("Attempt to dereference uninitated transaction");
+        case DEACTREF:
+          throw new IllegalStateException("Attempt to dereference deactivated transaction");
+      }
+    } catch (MulgaraTransactionException em) {
+      throw em;
+    } catch (Throwable th) {
+      throw implicitRollback(th);
+    } finally {
+      report("Dereferenced Transaction");
+    }
+  }
+
+  private void startTransaction() throws MulgaraTransactionException {
+    report("Initiating transaction");
+    try {
+      transaction = factory.transactionStart(this);
+      synchronized (this) {
+        currentThread = Thread.currentThread();
+      }
+    } catch (Throwable th) {
+      throw abortTransaction("Failed to start transaction", th);
+    }
+  }
+
+  private void resumeTransaction() throws MulgaraTransactionException {
+//    report("Resuming transaction");
+    try {
+      factory.transactionResumed(this, transaction);
+      synchronized (this) {
+        currentThread = Thread.currentThread();
+      }
+    } catch (Throwable th) {
+      abortTransaction("Failed to resume transaction", th);
+    }
+  }
+
+  private void suspendTransaction() throws MulgaraTransactionException {
+//    report("Suspending Transaction");
+    try {
+      if (using < 1) {
+        throw implicitRollback(
+            new MulgaraTransactionException("Attempt to suspend unreferenced transaction"));
+      }
+      transaction = factory.transactionSuspended(this);
+      synchronized (this) {
+        currentThread = null;
+      }
+      state = State.DEACTREF;
+    } catch (Throwable th) {
+      throw implicitRollback(th);
+    } finally {
+//      report("Finished suspending transaction");
+    }
+  }
+
+  public void commitTransaction() throws MulgaraTransactionException {
+    report("Committing Transaction");
+    try {
+      transaction.commit();
+    } catch (Throwable th) {
+      throw implicitRollback(th);
+    }
+    try {
+      try {
+        transaction = null;
+      } finally { try {
+        state = State.FINISHED;
+      } finally { try {
+        context.clear();
+      } finally { try {
+        enlisted.clear();
+      } finally { try {
+        factory.transactionComplete(this);
+      } finally { try {
+        factory = null;
+      } finally {
+        report("Committed transaction");
+      } } } } } }
+    } catch (Throwable th) {
+      errorReport("Error cleaning up transaction post-commit", th);
+      throw new MulgaraTransactionException("Error cleaning up transaction post-commit", th);
+    }
+  }
+
+  public void heuristicRollback(String cause) throws MulgaraTransactionException {
+    implicitRollback(new MulgaraTransactionException(cause));
+  }
+
+  /**
+   * Rollback the transaction.
+   * We don't throw an exception here when transaction fails - this is expected,
+   * after all we requested it.
+   */
+  public void explicitRollback() throws MulgaraTransactionException {
+    synchronized (this) {
+      if (currentThread == null) {
+        throw new MulgaraTransactionException("Transaction failed activation check");
+      } else if (!currentThread.equals(Thread.currentThread())) {
+        throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+      }
+    }
+
+    try {
+      switch (state) {
+        case ACTUNREF:
+        case ACTREF:
+          transaction.rollback();
+          context.clear();
+          enlisted.clear();
+          factory.transactionComplete(this);
+          state = State.FINISHED;
+          break;
+        case DEACTREF:
+          throw new IllegalStateException("Attempt to rollback unactivated transaction");
+        case CONSTRUCTEDREF:
+          throw new MulgaraTransactionException("Attempt to rollback uninitiated ref'd transaction");
+        case CONSTRUCTEDUNREF:
+          throw new MulgaraTransactionException("Attempt to rollback uninitiated unref'd transaction");
+        case FINISHED:
+          throw new MulgaraTransactionException("Attempt to rollback finished transaction");
+        case FAILED:
+          throw new MulgaraTransactionException("Attempt to rollback failed transaction");
+      }
+    } catch (MulgaraTransactionException em) {
+      throw em;
+    } catch (Throwable th) {
+      throw implicitRollback(th);
+    }
+  }
+
+  /**
+   * This will endevour to terminate the transaction via a rollback - if this
+   * fails it will abort the transaction.
+   * If the rollback succeeds then this method will return a suitable
+   * MulgaraTransactionException to be thrown by the caller.
+   * If the rollback fails then this method will throw the resulting exception
+   * from abortTransaction().
+   * Post-condition: The transaction is terminated and cleaned up.
+   */
+  MulgaraTransactionException implicitRollback(Throwable cause) throws MulgaraTransactionException {
+    try {
+      report("Implicit Rollback triggered");
+
+      synchronized (this) {
+        if (currentThread == null) {
+          throw new MulgaraTransactionException("Transaction not associated with thread");
+        } else if (!currentThread.equals(Thread.currentThread())) {
+          throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+        }
+      }
+
+      if (rollbackCause != null) {
+        errorReport("Cascading error, transaction already rolled back", cause);
+        errorReport("Cascade error, expected initial cause", rollbackCause);
+
+        return new MulgaraTransactionException("Transaction already in rollback", cause);
+      }
+
+      switch (state) {
+        case ACTUNREF:
+        case ACTREF:
+            rollbackCause = cause;
+            transaction.rollback();
+            transaction = null;
+            context.clear();
+            enlisted.clear();
+            state = State.FAILED;
+            factory.transactionComplete(this);
+            factory = null;
+            return new MulgaraTransactionException("Transaction rollback triggered", cause);
+        case DEACTREF:
+          throw new IllegalStateException("Attempt to rollback deactivated transaction");
+        case CONSTRUCTEDREF:
+          throw new MulgaraTransactionException("Attempt to rollback uninitiated ref'd transaction");
+        case CONSTRUCTEDUNREF:
+          throw new MulgaraTransactionException("Attempt to rollback uninitiated unref'd transaction");
+        case FINISHED:
+          throw new MulgaraTransactionException("Attempt to rollback finished transaction");
+        case FAILED:
+          throw new MulgaraTransactionException("Attempt to rollback failed transaction");
+        default:
+          throw new MulgaraTransactionException("Unknown state");
+      }
+    } catch (Throwable th) {
+      try {
+        errorReport("Attempt to rollback failed; initiating cause: ", cause);
+      } finally {
+        throw abortTransaction("Failed to rollback normally - see log for inititing cause", th);
+      }
+    } finally {
+      report("Leaving implicitRollback");
+    }
+  }
+
+  /**
+   * Forces the transaction to be abandoned, including bypassing JTA to directly
+   * rollback/abort the underlying store-phases if required.
+   * Heavilly nested try{}finally{} should guarentee that even JVM errors should
+   * not prevent this function from cleaning up anything that can be cleaned up.
+   * We have to delegate to the OperationContext the abort() on the resolvers as
+   * only it has full knowledge of which resolvers are associated with this
+   * transaction.
+   */
+  public MulgaraTransactionException abortTransaction(String errorMessage, Throwable cause)
+      throws MulgaraTransactionException {
+    // We need to notify the factory here - this is serious, we
+    // need to rollback this transaction, but if we have reached here
+    // we have failed to obtain a valid transaction to rollback!
+    try {
+      try {
+        errorReport(errorMessage + " - Aborting", cause);
+      } finally { try {
+        if (transaction != null) {
+          transaction.rollback();
+        }
+      } finally { try {
+        factory.transactionAborted(this);
+      } finally { try {
+        abortEnlistedResources();
+      } finally { try {
+        context.clear();
+      } finally { try {
+        enlisted.clear();
+      } finally { try {
+        transaction = null;
+      } finally { try {
+        factory = null;
+      } finally {
+        state = State.FAILED;
+      } } } } } } } }
+      return new MulgaraTransactionException(errorMessage + " - Aborting", cause);
+    } catch (Throwable th) {
+      throw new MulgaraTransactionException(errorMessage + " - Failed to abort cleanly", th);
+    } finally {
+      report("Leaving abortTransaction");
+    }
+  }
+
+  /**
+   * Used to bypass JTA and explicitly abort resources behind the scenes.
+   */
+  private void abortEnlistedResources() {
+    for (EnlistableResource e : enlisted) {
+      try {
+        e.abort();
+      } catch (Throwable th) {
+        try {
+          errorReport("Error aborting enlistable resource", th);
+        } catch (Throwable ignore) { }
+      }
+    }
+  }
+
+  public void execute(Operation operation,
+               ResolverSessionFactory resolverSessionFactory, // FIXME: We shouldn't need this. - only used for backup and restore operations.
+               DatabaseMetadata metadata) throws MulgaraTransactionException {
+    report("Executing Operation");
+    try {
+      activate();
+      try {
+        operation.execute(context,
+                          context.getSystemResolver(),
+                          resolverSessionFactory,
+                          metadata);
+      } catch (Throwable th) {
+        throw implicitRollback(th);
+      } finally {
+        deactivate();
+      }
+    } finally {
+      report("Executed Operation");
+    }
+  }
+
+  public AnswerOperationResult execute(AnswerOperation ao) throws TuplesException {
+    debugReport("Executing AnswerOperation");
+    try {
+      activate();
+      try {
+        ao.execute();
+        return ao.getResult();
+      } catch (Throwable th) {
+        throw implicitRollback(th);
+      } finally {
+        deactivate();
+      }
+    } catch (MulgaraTransactionException em) {
+      throw new TuplesException("Transaction error", em);
+    } finally {
+      debugReport("Executed AnswerOperation");
+    }
+  }
+
+
+  public void execute(TransactionOperation to) throws MulgaraTransactionException {
+    report("Executing TransactionOperation");
+    try {
+      activate();
+      try {
+        to.execute();
+      } catch (Throwable th) {
+        throw implicitRollback(th);
+      } finally {
+        deactivate();
+      }
+    } finally {
+      report("Executed TransactionOperation");
+    }
+  }
+
+  public void enlist(EnlistableResource enlistable) throws MulgaraTransactionException {
+    try {
+      synchronized (this) {
+        if (currentThread == null) {
+          throw new MulgaraTransactionException("Transaction not associated with thread");
+        } else if (!currentThread.equals(Thread.currentThread())) {
+          throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+        }
+      }
+
+      if (enlisted.contains(enlistable)) {
+        return;
+      }
+
+      switch (state) {
+        case ACTUNREF:
+        case ACTREF:
+          transaction.enlistResource(enlistable.getXAResource());
+          enlisted.add(enlistable);
+          break;
+        case CONSTRUCTEDREF:
+          throw new MulgaraTransactionException("Attempt to enlist resource in uninitated ref'd transaction");
+        case CONSTRUCTEDUNREF:
+          throw new MulgaraTransactionException("Attempt to enlist resource in uninitated unref'd transaction");
+        case DEACTREF:
+          throw new MulgaraTransactionException("Attempt to enlist resource in unactivated transaction");
+        case FINISHED:
+          throw new MulgaraTransactionException("Attempt to enlist resource in finished transaction");
+        case FAILED:
+          throw new MulgaraTransactionException("Attempt to enlist resource in failed transaction");
+      }
+    } catch (Throwable th) {
+      throw implicitRollback(th);
+    }
+  }
+
+  //
+  // Used internally
+  //
+
+  private void checkActivated() throws MulgaraTransactionException {
+    synchronized (this) {
+      if (currentThread == null) {
+        throw new MulgaraTransactionException("Transaction not associated with thread");
+      } else if (!currentThread.equals(Thread.currentThread())) {
+        throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+      }
+    }
+
+    switch (state) {
+      case ACTUNREF:
+      case ACTREF:
+        if (inuse < 0 || using < 0) {
+          throw new MulgaraTransactionException("Reference Failure, using: " + using + ", inuse: " + inuse);
+        }
+        return;
+      case CONSTRUCTEDREF:
+        throw new MulgaraTransactionException("Transaction (ref) uninitiated");
+      case CONSTRUCTEDUNREF:
+        throw new MulgaraTransactionException("Transaction (unref) uninitiated");
+      case DEACTREF:
+        throw new MulgaraTransactionException("Transaction deactivated");
+      case FINISHED:
+        throw new MulgaraTransactionException("Transaction is terminated");
+      case FAILED:
+        throw new MulgaraTransactionException("Transaction is failed", rollbackCause);
+    }
+  }
+
+  private void acquireActivationMutex() {
+    activationMutex.lock();
+  }
+
+  private void releaseActivationMutex() {
+    activationMutex.unlock();
+  }
+
+  protected void finalize() {
+    report("GC-finalize");
+    if (state != State.FINISHED && state != State.FAILED) {
+      errorReport("Finalizing incomplete transaction - aborting...", null);
+      try {
+        abortTransaction("Transaction finalized while still valid", new Throwable());
+      } catch (Throwable th) {
+        errorReport("Attempt to abort transaction from finalize failed", th);
+      }
+    }
+
+    if (state != State.FAILED && (inuse != 0 || using != 0)) {
+      errorReport("Reference counting error in transaction", null);
+    }
+
+    if (factory != null || transaction != null) {
+      errorReport("Transaction not terminated properly", null);
+    }
+  }
+
+  private void report(String desc) {
+    if (logger.isInfoEnabled()) {
+      logger.info(desc + ": " + System.identityHashCode(this) + ", state=" + state +
+          ", inuse=" + inuse + ", using=" + using);
+    }
+  }
+
+  private void debugReport(String desc) {
+    if (logger.isDebugEnabled()) {
+      logger.debug(desc + ": " + System.identityHashCode(this) + ", state=" + state +
+          ", inuse=" + inuse + ", using=" + using);
+    }
+  }
+
+  private void errorReport(String desc, Throwable cause) {
+    logger.error(desc + ": " + System.identityHashCode(this) + ", state=" + state +
+        ", inuse=" + inuse + ", using=" + using, cause != null ? cause : new Throwable());
+  }
+}

Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransactionFactory.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransactionFactory.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransactionFactory.java	                        (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransactionFactory.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,421 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com). Portions created
+ * by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd.
+ * All Rights Reserved.
+ *
+ * Work deriving from MulgaraTransactionManager Copyright (c) 2007 Topaz
+ * Foundation under contract by Andrae Muys (mailto:andrae at netymon.com).
+ */
+
+package org.mulgara.resolver;
+
+// Java2 packages
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+// Local packages
+import org.mulgara.query.MulgaraTransactionException;
+import org.mulgara.transaction.TransactionManagerFactory;
+import org.mulgara.util.Assoc1toNMap;
+
+/**
+ * Implements the internal transaction controls offered by Session.
+ *
+ * @created 2006-10-06
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ *
+ * @copyright &copy;2006 <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
+ *
+ * @licence Open Software License v3.0</a>
+ */
+
+public class MulgaraInternalTransactionFactory extends MulgaraTransactionFactory {
+  /** Logger.  */
+  private static final Logger logger =
+    Logger.getLogger(MulgaraInternalTransactionFactory.class.getName());
+
+  private boolean autoCommit;
+
+  /** Set of sessions whose transactions have been rolledback.*/
+  private Set<DatabaseSession> failedSessions;
+
+  /** Map of threads to active transactions. */
+  private Map<Thread, MulgaraTransaction> activeTransactions;
+
+  private Assoc1toNMap<DatabaseSession, MulgaraTransaction> sessionXAMap;
+
+  private final TransactionManager transactionManager;
+
+  public MulgaraInternalTransactionFactory(MulgaraTransactionManager manager, TransactionManagerFactory transactionManagerFactory) {
+    super(manager);
+    this.autoCommit = true;
+
+    this.failedSessions = new HashSet<DatabaseSession>();
+    this.activeTransactions = new HashMap<Thread, MulgaraTransaction>();
+    this.sessionXAMap = new Assoc1toNMap<DatabaseSession, MulgaraTransaction>();
+
+    this.transactionManager = transactionManagerFactory.newTransactionManager();
+  }
+
+  public MulgaraTransaction getTransaction(final DatabaseSession session, boolean write)
+      throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      if (manager.isHoldingWriteLock(session)) {
+        return writeTransaction;
+      }
+
+      try {
+        MulgaraInternalTransaction transaction;
+        if (write) {
+          runWithoutMutex(new TransactionOperation() {
+            public void execute() throws MulgaraTransactionException {
+              manager.obtainWriteLock(session);
+            }
+          });
+          try {
+            assert writeTransaction == null;
+            writeTransaction = transaction = 
+                new MulgaraInternalTransaction(this, session.newOperationContext(true));
+          } catch (Throwable th) {
+            manager.releaseWriteLock(session);
+            throw new MulgaraTransactionException("Error creating write transaction", th);
+          }
+        } else {
+          transaction = new MulgaraInternalTransaction(this, session.newOperationContext(false));
+        }
+
+        sessionXAMap.put(session, transaction);
+
+        return transaction;
+      } catch (MulgaraTransactionException em) {
+        throw em;
+      } catch (Exception e) {
+        throw new MulgaraTransactionException("Error creating transaction", e);
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public Set<MulgaraTransaction> getTransactionsForSession(DatabaseSession session) {
+    acquireMutex();
+    try {
+      Set <MulgaraTransaction> xas = sessionXAMap.getN(session);
+      return xas == null ? Collections.<MulgaraTransaction>emptySet() : xas;
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public MulgaraTransaction newMulgaraTransaction(DatabaseOperationContext context)
+      throws MulgaraTransactionException {
+    return new MulgaraInternalTransaction(this, context);
+  }
+
+
+  public void commit(DatabaseSession session) throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      manager.reserveWriteLock(session);
+      try {
+        if (failedSessions.contains(session)) {
+          throw new MulgaraTransactionException("Attempting to commit failed exception");
+        } else if (!manager.isHoldingWriteLock(session)) {
+          throw new MulgaraTransactionException(
+              "Attempting to commit while not the current writing transaction");
+        }
+
+        setAutoCommit(session, true);
+        setAutoCommit(session, false);
+      } finally {
+        manager.releaseReserve(session);
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+
+  /**
+   * This is an explicit, user-specified rollback.
+   * 
+   * This needs to be distinguished from an implicit rollback triggered by failure.
+   */
+  public void rollback(DatabaseSession session) throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      manager.reserveWriteLock(session);
+      try {
+        if (manager.isHoldingWriteLock(session)) {
+          try {
+            writeTransaction.execute(new TransactionOperation() {
+                public void execute() throws MulgaraTransactionException {
+                  writeTransaction.heuristicRollback("Explicit Rollback");
+                }
+            });
+            // FIXME: Should be checking status here, not writelock.
+            if (manager.isHoldingWriteLock(session)) {
+              // transaction referenced by something - need to explicitly end it.
+              writeTransaction.abortTransaction("Rollback failed",
+                  new MulgaraTransactionException("Rollback failed to terminate write transaction"));
+            }
+          } finally {
+            failedSessions.add(session);
+            setAutoCommit(session, false);
+          }
+        } else if (failedSessions.contains(session)) {
+          failedSessions.remove(session);
+          setAutoCommit(session, false);
+        } else {
+          throw new MulgaraTransactionException(
+              "Attempt to rollback while not in the current writing transaction");
+        }
+      } finally {
+        manager.releaseReserve(session);
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public void setAutoCommit(DatabaseSession session, boolean autoCommit)
+      throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      if (manager.isHoldingWriteLock(session) && failedSessions.contains(session)) {
+        writeTransaction.abortTransaction("Session failed and still holding writeLock",
+            new MulgaraTransactionException("Failed Session in setAutoCommit"));
+      }
+
+      if (manager.isHoldingWriteLock(session) || failedSessions.contains(session)) {
+        if (autoCommit) {
+          // AutoCommit off -> on === branch on current state of transaction.
+          if (manager.isHoldingWriteLock(session)) {
+            // Within active transaction - commit and finalise.
+            try {
+              runWithoutMutex(new TransactionOperation() {
+                public void execute() throws MulgaraTransactionException {
+                  writeTransaction.execute(new TransactionOperation() {
+                    public void execute() throws MulgaraTransactionException {
+                      writeTransaction.dereference();
+                      ((MulgaraInternalTransaction)writeTransaction).commitTransaction();
+                    }
+                  });
+                }
+              });
+            } finally {
+              // This should have been cleaned up by the commit above, but if it
+              // hasn't then if we don't release here we could deadlock the
+              // transaction manager
+              if (manager.isHoldingWriteLock(session)) {
+                manager.releaseWriteLock(session);
+              }
+              this.autoCommit = true;
+            }
+          } else if (failedSessions.contains(session)) {
+            // Within failed transaction - cleanup.
+            failedSessions.remove(session);
+          }
+        } else {
+          if (!manager.isHoldingWriteLock(session)) {
+            if (failedSessions.contains(session)) {
+              failedSessions.remove(session);
+              setAutoCommit(session, false);
+            } else {
+              throw new IllegalStateException("Can't reach here");
+            }
+          } else {
+            // AutoCommit off -> off === no-op. Log info.
+            if (logger.isInfoEnabled()) {
+              logger.info("Attempt to set autocommit false twice", new Throwable());
+            }
+          }
+        }
+      } else {
+        if (autoCommit) {
+          // AutoCommit on -> on === no-op.  Log info.
+          logger.info("Attempting to set autocommit true without setting it false");
+        } else {
+          // AutoCommit on -> off == Start new transaction.
+          getTransaction(session, true); // Set's writeTransaction.
+          writeTransaction.reference();
+          this.autoCommit = false;
+        }
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  //
+  // Transaction livecycle callbacks.
+  //
+
+  public Transaction transactionStart(MulgaraTransaction transaction) throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      try {
+        logger.info("Beginning Transaction");
+        if (activeTransactions.get(Thread.currentThread()) != null) {
+          throw new MulgaraTransactionException(
+              "Attempt to start transaction in thread with exiting active transaction.");
+        } else if (activeTransactions.containsValue(transaction)) {
+          throw new MulgaraTransactionException("Attempt to start transaction twice");
+        }
+
+        transactionManager.begin();
+        Transaction jtaTrans = transactionManager.getTransaction();
+
+        activeTransactions.put(Thread.currentThread(), transaction);
+
+        return jtaTrans;
+      } catch (Exception e) {
+        throw new MulgaraTransactionException("Transaction Begin Failed", e);
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public void transactionResumed(MulgaraTransaction transaction, Transaction jtaXA) 
+      throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      if (activeTransactions.get(Thread.currentThread()) != null) {
+        throw new MulgaraTransactionException(
+            "Attempt to resume transaction in already activated thread");
+      } else if (activeTransactions.containsValue(transaction)) {
+        throw new MulgaraTransactionException("Attempt to resume active transaction");
+      }
+      
+      try {
+        transactionManager.resume(jtaXA);
+        activeTransactions.put(Thread.currentThread(), transaction);
+      } catch (Exception e) {
+        throw new MulgaraTransactionException("Resume Failed", e);
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public Transaction transactionSuspended(MulgaraTransaction transaction)
+      throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      try {
+        if (transaction != activeTransactions.get(Thread.currentThread())) {
+          throw new MulgaraTransactionException(
+              "Attempt to suspend transaction from outside thread");
+        }
+
+        if (autoCommit && transaction == writeTransaction) {
+          logger.error("Attempt to suspend write transaction without setting AutoCommit Off");
+          throw new MulgaraTransactionException(
+              "Attempt to suspend write transaction without setting AutoCommit Off");
+        }
+
+        Transaction xa = transactionManager.suspend();
+        activeTransactions.remove(Thread.currentThread());
+
+        return xa;
+      } catch (Throwable th) {
+        logger.error("Attempt to suspend failed", th);
+        try {
+          transactionManager.setRollbackOnly();
+        } catch (Throwable t) {
+          logger.error("Attempt to setRollbackOnly() failed", t);
+        }
+        throw new MulgaraTransactionException("Suspend failed", th);
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public void transactionComplete(MulgaraTransaction transaction) throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      logger.debug("Transaction Complete");
+      DatabaseSession session = sessionXAMap.get1(transaction);
+      if (session == null) {
+        throw new MulgaraTransactionException("No associated session found for transaction");
+      }
+      if (manager.isHoldingWriteLock(session)) {
+        manager.releaseWriteLock(session);
+        writeTransaction = null;
+      }
+
+      sessionXAMap.removeN(transaction);
+      activeTransactions.remove(Thread.currentThread());
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public void transactionAborted(MulgaraTransaction transaction) {
+    acquireMutex();
+    try {
+      try {
+        // Make sure this cleans up the transaction metadata - this transaction is DEAD!
+        if (transaction == writeTransaction) {
+          failedSessions.add(sessionXAMap.get1(transaction));
+        }
+        transactionComplete(transaction);
+      } catch (Throwable th) {
+        // FIXME: This should probably abort the entire server after logging the error!
+        logger.error("Error managing transaction abort", th);
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  public void setTransactionTimeout(int transactionTimeout) {
+    try {
+      transactionManager.setTransactionTimeout(transactionTimeout);
+    } catch (SystemException es) {
+      logger.warn("Unable to set transaction timeout: " + transactionTimeout, es);
+    }
+  }
+
+  void abortWriteTransaction() throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      if (writeTransaction != null) {
+        writeTransaction.abortTransaction("Explicit abort requested by write-lock manager", new Throwable());
+        writeTransaction = null;
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+}

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransaction.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransaction.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransaction.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -11,16 +11,15 @@
  *
  * This file is an original work developed by Netymon Pty Ltd
  * (http://www.netymon.com, mailto:mail at netymon.com). Portions created
- * by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd.
+ * by Netymon Pty Ltd are Copyright (c) 2007 Netymon Pty Ltd.
  * All Rights Reserved.
+ *
+ * Migration to interface Copyright (c) 2007 Topaz Foundation
+ * under contract by Andrae Muys (mailto:andrae at netymon.com).
  */
 package org.mulgara.resolver;
 
 // Java 2 enterprise packages
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.locks.ReentrantLock;
-import javax.transaction.Transaction;
 
 // Third party packages
 import org.apache.log4j.Logger;
@@ -34,17 +33,8 @@
 import org.mulgara.query.TuplesException;
 
 /**
- * Responsible for the javax.transaction.Transaction object.
- * Responsibilities
- * Ensuring every begin or resume is followed by either a suspend or an end.
- * Ensuring every suspend or end is preceeded by either a begin or a resume.
- * In conjunction with TransactionalAnswer ensuring that
- * all calls to operations on SubqueryAnswer are preceeded by a successful resume.
- * all calls to operations on SubqueryAnswer conclude with a suspend as the last call prior to returning to the user.
- * Collaborates with DatabaseTransactionManager to determine when to end the transaction.
+ * @created 2007-11-06
  *
- * @created 2006-10-06
- *
  * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
  *
  * @version $Revision: $
@@ -55,682 +45,52 @@
  *
  * @company <a href="mailto:mail at netymon.com">Netymon Pty Ltd</a>
  *
- * @copyright &copy;2006 <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
+ * @copyright &copy;2007 <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
  *
  * @licence Open Software License v3.0
  */
-public class MulgaraTransaction {
+public interface MulgaraTransaction {
+  void reference() throws MulgaraTransactionException;
+  void dereference() throws MulgaraTransactionException;
+
   /**
-   * This is the state machine switch matching these states.
-      switch (state) {
-        case CONSTRUCTEDREF:
-        case CONSTRUCTEDUNREF:
-        case ACTUNREF:
-        case ACTREF:
-        case DEACTREF:
-        case FINISHED:
-        case FAILED:
-      }
+   * Forces the transaction to be abandoned, including bypassing JTA to directly
+   * rollback/abort the underlying store-phases if required.
+   * This this transaction is externally managed this amounts to a heuristic
+   * rollback decision and should be treated as such.
+   *
+   * @return an exception constructed with the provided error-message and cause.
+   * @throws MulgaraTransactionException if a further error is encounted while
+   * attempting to abort.
    */
-  private enum State { CONSTRUCTEDREF, CONSTRUCTEDUNREF, ACTUNREF, ACTREF, DEACTREF, FINISHED, FAILED };
+  MulgaraTransactionException abortTransaction(String errorMessage, Throwable cause) throws MulgaraTransactionException;
 
-  /** Logger.  */
-  private static final Logger logger =
-    Logger.getLogger(MulgaraTransaction.class.getName());
+  void heuristicRollback(String cause) throws MulgaraTransactionException;
 
-  private MulgaraTransactionManager manager;
-  private DatabaseOperationContext context;
-  private Set<EnlistableResource> enlisted;
-
-  private Transaction transaction;
-  private Thread currentThread;
-
-  private ReentrantLock activationMutex;
-
-  private State state;
-  private int inuse;
-  private int using;
-
-  private Throwable rollbackCause;
-
-  public MulgaraTransaction(MulgaraTransactionManager manager, DatabaseOperationContext context)
-      throws IllegalArgumentException {
-    report("Creating Transaction");
-
-    try {
-      if (manager == null) {
-        throw new IllegalArgumentException("Manager null in MulgaraTransaction");
-      } else if (context == null) {
-        throw new IllegalArgumentException("OperationContext null in MulgaraTransaction");
-      }
-      this.manager = manager;
-      this.context = context;
-      this.enlisted = new HashSet<EnlistableResource>();
-      this.activationMutex = new ReentrantLock();
-      this.currentThread = null;
-
-      inuse = 0;
-      using = 0;
-      state = State.CONSTRUCTEDUNREF;
-      rollbackCause = null;
-    } finally {
-      report("Finished Creating Transaction");
-    }
-  }
-
-
-  void activate() throws MulgaraTransactionException {
-//    report("Activating Transaction");
-    try {
-      synchronized (this) {
-        if (currentThread != null && !currentThread.equals(Thread.currentThread())) {
-          throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
-        }
-      }
-      
-      acquireActivationMutex();
-      try {
-        switch (state) {
-          case CONSTRUCTEDUNREF:
-            startTransaction();
-            inuse = 1;
-            state = State.ACTUNREF;
-            try {
-              context.initiate(this);
-            } catch (Throwable th) {
-              throw implicitRollback(th);
-            }
-            break;
-          case CONSTRUCTEDREF:
-            startTransaction();
-            inuse = 1;
-            using = 1;
-            state = State.ACTREF;
-            try {
-              context.initiate(this);
-            } catch (Throwable th) {
-              throw implicitRollback(th);
-            }
-            break;
-          case DEACTREF:
-            resumeTransaction();
-            inuse = 1;
-            state = State.ACTREF;
-            break;
-          case ACTREF:
-          case ACTUNREF:
-            inuse++;
-            break;
-          case FINISHED:
-            throw new MulgaraTransactionException("Attempt to activate terminated transaction");
-          case FAILED:
-            throw new MulgaraTransactionException("Attempt to activate failed transaction", rollbackCause);
-        }
-      } finally {
-        releaseActivationMutex();
-      }
-
-      try {
-        checkActivated();
-      } catch (MulgaraTransactionException em) {
-        throw abortTransaction("Activate failed post-condition check", em);
-      }
-    } catch (MulgaraTransactionException em) {
-      throw em;
-    } catch (Throwable th) {
-      throw abortTransaction("Error activating transaction", th);
-    } finally {
-//      report("Leaving Activate transaction");
-    }
-  }
-
-
-  private void deactivate() throws MulgaraTransactionException {
-//    report("Deactivating transaction");
-
-    try {
-      synchronized (this) {
-        if (currentThread == null) {
-          throw new MulgaraTransactionException("Transaction not associated with thread");
-        } else if (!currentThread.equals(Thread.currentThread())) {
-          throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
-        }
-      }
-      
-      acquireActivationMutex();
-      try {
-        switch (state) {
-          case ACTUNREF:
-            if (inuse == 1) {
-              commitTransaction();
-            }
-            inuse--;
-            break;
-          case ACTREF:
-            if (inuse == 1) {
-              suspendTransaction();
-            }
-            inuse--;
-            break;
-          case CONSTRUCTEDREF:
-            throw new MulgaraTransactionException("Attempt to deactivate uninitiated refed transaction");
-          case CONSTRUCTEDUNREF:
-            throw new MulgaraTransactionException("Attempt to deactivate uninitiated transaction");
-          case DEACTREF:
-            throw new IllegalStateException("Attempt to deactivate unactivated transaction");
-          case FINISHED:
-            if (inuse < 0) {
-              errorReport("Activation count failure - too many deacts - in finished transaction", null);
-            } else {
-              inuse--;
-            }
-            break;
-          case FAILED:
-            // Nothing to do here.
-            break;
-        }
-      } finally {
-        releaseActivationMutex();
-      }
-    } catch (MulgaraTransactionException em) {
-      throw em;
-    } catch (Throwable th) {
-      throw abortTransaction("Error deactivating transaction", th);
-    } finally {
-//      report("Leaving Deactivate Transaction");
-    }
-  }
-
-  // Note: The transaction is often not activated when these are called.
-  //       This occurs when setting autocommit, as this creates and
-  //       references a transaction object that won't be started/activated
-  //       until it is first used.
-  void reference() throws MulgaraTransactionException {
-    try {
-      report("Referencing Transaction");
-
-      synchronized (this) {
-        if (currentThread != null && !currentThread.equals(Thread.currentThread())) {
-          throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
-        }
-      }
-
-      switch (state) {
-        case CONSTRUCTEDUNREF:
-          state = State.CONSTRUCTEDREF;
-          break;
-        case ACTREF:
-        case ACTUNREF:
-          using++;
-          state = State.ACTREF;
-          break;
-        case DEACTREF:
-          using++;
-          break;
-        case CONSTRUCTEDREF:
-          throw new MulgaraTransactionException("Attempt to reference uninitated transaction twice");
-        case FINISHED:
-          throw new MulgaraTransactionException("Attempt to reference terminated transaction");
-        case FAILED:
-          throw new MulgaraTransactionException("Attempt to reference failed transaction", rollbackCause);
-      }
-    } catch (MulgaraTransactionException em) {
-      throw em;
-    } catch (Throwable th) {
-      report("Error referencing transaction");
-      throw implicitRollback(th);
-    } finally {
-      report("Leaving Reference Transaction");
-    }
-  }
-
-  void dereference() throws MulgaraTransactionException {
-    report("Dereferencing Transaction");
-    try {
-      synchronized (this) {
-        if (currentThread != null && !currentThread.equals(Thread.currentThread())) {
-          throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
-        }
-      }
-
-      switch (state) {
-        case ACTREF:
-          if (using == 1) {
-            state = State.ACTUNREF;
-          }
-          using--;
-          break;
-        case CONSTRUCTEDREF:
-          state = State.CONSTRUCTEDUNREF;
-          break;
-        case FINISHED:
-        case FAILED:
-          if (using < 1) {
-            errorReport("Reference count failure - too many derefs - in finished transaction", null);
-          } else {
-            using--;
-          }
-          break;
-        case ACTUNREF:
-          throw new IllegalStateException("Attempt to dereference unreferenced transaction");
-        case CONSTRUCTEDUNREF:
-          throw new MulgaraTransactionException("Attempt to dereference uninitated transaction");
-        case DEACTREF:
-          throw new IllegalStateException("Attempt to dereference deactivated transaction");
-      }
-    } catch (MulgaraTransactionException em) {
-      throw em;
-    } catch (Throwable th) {
-      throw implicitRollback(th);
-    } finally {
-      report("Dereferenced Transaction");
-    }
-  }
-
-  private void startTransaction() throws MulgaraTransactionException {
-    report("Initiating transaction");
-    try {
-      transaction = manager.transactionStart(this);
-      synchronized (this) {
-        currentThread = Thread.currentThread();
-      }
-    } catch (Throwable th) {
-      throw abortTransaction("Failed to start transaction", th);
-    }
-  }
-
-  private void resumeTransaction() throws MulgaraTransactionException {
-//    report("Resuming transaction");
-    try {
-      manager.transactionResumed(this, transaction);
-      synchronized (this) {
-        currentThread = Thread.currentThread();
-      }
-    } catch (Throwable th) {
-      abortTransaction("Failed to resume transaction", th);
-    }
-  }
-
-  private void suspendTransaction() throws MulgaraTransactionException {
-//    report("Suspending Transaction");
-    try {
-      if (using < 1) {
-        throw implicitRollback(
-            new MulgaraTransactionException("Attempt to suspend unreferenced transaction"));
-      }
-      transaction = manager.transactionSuspended(this);
-      synchronized (this) {
-        currentThread = null;
-      }
-      state = State.DEACTREF;
-    } catch (Throwable th) {
-      throw implicitRollback(th);
-    } finally {
-//      report("Finished suspending transaction");
-    }
-  }
-
-  public void commitTransaction() throws MulgaraTransactionException {
-    report("Committing Transaction");
-    try {
-      transaction.commit();
-    } catch (Throwable th) {
-      throw implicitRollback(th);
-    }
-    try {
-      try {
-        transaction = null;
-      } finally { try {
-        state = State.FINISHED;
-      } finally { try {
-        context.clear();
-      } finally { try {
-        enlisted.clear();
-      } finally { try {
-        manager.transactionComplete(this);
-      } finally { try {
-        manager = null;
-      } finally {
-        report("Committed transaction");
-      } } } } } }
-    } catch (Throwable th) {
-      errorReport("Error cleaning up transaction post-commit", th);
-      throw new MulgaraTransactionException("Error cleaning up transaction post-commit", th);
-    }
-  }
-
   /**
-   * Rollback the transaction.
-   * We don't throw an exception here when transaction fails - this is expected,
-   * after all we requested it.
+   * Execute the specified operation.
+   *
+   * FIXME: We shouldn't need resolverSessionFactory as this is only used for backup and restore operations.
    */
-  public void explicitRollback() throws MulgaraTransactionException {
-    synchronized (this) {
-      if (currentThread == null) {
-        throw new MulgaraTransactionException("Transaction failed activation check");
-      } else if (!currentThread.equals(Thread.currentThread())) {
-        throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
-      }
-    }
+  void execute(Operation operation,
+               ResolverSessionFactory resolverSessionFactory,
+               DatabaseMetadata metadata) throws MulgaraTransactionException;
 
-    try {
-      switch (state) {
-        case ACTUNREF:
-        case ACTREF:
-          transaction.rollback();
-          context.clear();
-          enlisted.clear();
-          manager.transactionComplete(this);
-          state = State.FINISHED;
-          break;
-        case DEACTREF:
-          throw new IllegalStateException("Attempt to rollback unactivated transaction");
-        case CONSTRUCTEDREF:
-          throw new MulgaraTransactionException("Attempt to rollback uninitiated ref'd transaction");
-        case CONSTRUCTEDUNREF:
-          throw new MulgaraTransactionException("Attempt to rollback uninitiated unref'd transaction");
-        case FINISHED:
-          throw new MulgaraTransactionException("Attempt to rollback finished transaction");
-        case FAILED:
-          throw new MulgaraTransactionException("Attempt to rollback failed transaction");
-      }
-    } catch (MulgaraTransactionException em) {
-      throw em;
-    } catch (Throwable th) {
-      throw implicitRollback(th);
-    }
-  }
-
   /**
-   * This will endevour to terminate the transaction via a rollback - if this
-   * fails it will abort the transaction.
-   * If the rollback succeeds then this method will return a suitable
-   * MulgaraTransactionException to be thrown by the caller.
-   * If the rollback fails then this method will throw the resulting exception
-   * from abortTransaction().
-   * Post-condition: The transaction is terminated and cleaned up.
+   * Execute the specified operation.
+   * Used by TransactionalAnswer to ensure transactional guarantees are met when
+   * using a result whose transaction may be in a suspended state.
    */
-  MulgaraTransactionException implicitRollback(Throwable cause) throws MulgaraTransactionException {
-    try {
-      report("Implicit Rollback triggered");
+  AnswerOperationResult execute(AnswerOperation ao) throws TuplesException;
 
-      synchronized (this) {
-        if (currentThread == null) {
-          throw new MulgaraTransactionException("Transaction not associated with thread");
-        } else if (!currentThread.equals(Thread.currentThread())) {
-          throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
-        }
-      }
-
-      if (rollbackCause != null) {
-        errorReport("Cascading error, transaction already rolled back", cause);
-        errorReport("Cascade error, expected initial cause", rollbackCause);
-
-        return new MulgaraTransactionException("Transaction already in rollback", cause);
-      }
-
-      switch (state) {
-        case ACTUNREF:
-        case ACTREF:
-            rollbackCause = cause;
-            transaction.rollback();
-            transaction = null;
-            context.clear();
-            enlisted.clear();
-            state = State.FAILED;
-            manager.transactionComplete(this);
-            manager = null;
-            return new MulgaraTransactionException("Transaction rollback triggered", cause);
-        case DEACTREF:
-          throw new IllegalStateException("Attempt to rollback deactivated transaction");
-        case CONSTRUCTEDREF:
-          throw new MulgaraTransactionException("Attempt to rollback uninitiated ref'd transaction");
-        case CONSTRUCTEDUNREF:
-          throw new MulgaraTransactionException("Attempt to rollback uninitiated unref'd transaction");
-        case FINISHED:
-          throw new MulgaraTransactionException("Attempt to rollback finished transaction");
-        case FAILED:
-          throw new MulgaraTransactionException("Attempt to rollback failed transaction");
-        default:
-          throw new MulgaraTransactionException("Unknown state");
-      }
-    } catch (Throwable th) {
-      errorReport("Attempt to rollback failed; initiating cause: ", cause);
-      throw abortTransaction("Failed to rollback normally - see log for inititing cause", th);
-    } finally {
-      report("Leaving implicitRollback");
-    }
-  }
-
   /**
-   * Forces the transaction to be abandoned, including bypassing JTA to directly
-   * rollback/abort the underlying store-phases if required.
-   * Heavilly nested try{}finally{} should guarentee that even JVM errors should
-   * not prevent this function from cleaning up anything that can be cleaned up.
-   * We have to delegate to the OperationContext the abort() on the resolvers as
-   * only it has full knowledge of which resolvers are associated with this
-   * transaction.
+   * Used by the TransactionCoordinator/Manager.
    */
-  MulgaraTransactionException abortTransaction(String errorMessage, Throwable cause)
-      throws MulgaraTransactionException {
-    // We need to notify the manager here - this is serious, we
-    // need to rollback this transaction, but if we have reached here
-    // we have failed to obtain a valid transaction to rollback!
-    try {
-      try {
-        errorReport(errorMessage + " - Aborting", cause);
-      } finally { try {
-        if (transaction != null) {
-          transaction.rollback();
-        }
-      } finally { try {
-        manager.transactionAborted(this);
-      } finally { try {
-        abortEnlistedResources();
-      } finally { try {
-        context.clear();
-      } finally { try {
-        enlisted.clear();
-      } finally { try {
-        transaction = null;
-      } finally { try {
-        manager = null;
-      } finally {
-        state = State.FAILED;
-      } } } } } } } }
-      return new MulgaraTransactionException(errorMessage + " - Aborting", cause);
-    } catch (Throwable th) {
-      throw new MulgaraTransactionException(errorMessage + " - Failed to abort cleanly", th);
-    } finally {
-      report("Leaving abortTransaction");
-    }
-  }
+  void execute(TransactionOperation to) throws MulgaraTransactionException;
 
   /**
-   * Used to bypass JTA and explicitly abort resources behind the scenes.
+   * enlist an XAResource in this transaction - includes an extra method
+   * abort().
    */
-  private void abortEnlistedResources() {
-    for (EnlistableResource e : enlisted) {
-      try {
-        e.abort();
-      } catch (Throwable th) {
-        try {
-          errorReport("Error aborting enlistable resource", th);
-        } catch (Throwable ignore) { }
-      }
-    }
-  }
-
-  void execute(Operation operation,
-               ResolverSessionFactory resolverSessionFactory, // FIXME: We shouldn't need this. - only used for backup and restore operations.
-               DatabaseMetadata metadata) throws MulgaraTransactionException {
-    report("Executing Operation");
-    try {
-      activate();
-      try {
-        operation.execute(context,
-                          context.getSystemResolver(),
-                          resolverSessionFactory,
-                          metadata);
-      } catch (Throwable th) {
-        throw implicitRollback(th);
-      } finally {
-        deactivate();
-      }
-    } finally {
-      report("Executed Operation");
-    }
-  }
-
-  AnswerOperationResult execute(AnswerOperation ao) throws TuplesException {
-    debugReport("Executing AnswerOperation");
-    try {
-      activate();
-      try {
-        ao.execute();
-        return ao.getResult();
-      } catch (Throwable th) {
-        throw implicitRollback(th);
-      } finally {
-        deactivate();
-      }
-    } catch (MulgaraTransactionException em) {
-      throw new TuplesException("Transaction error", em);
-    } finally {
-      debugReport("Executed AnswerOperation");
-    }
-  }
-
-
-  void execute(TransactionOperation to) throws MulgaraTransactionException {
-    report("Executing TransactionOperation");
-    try {
-      activate();
-      try {
-        to.execute();
-      } catch (Throwable th) {
-        throw implicitRollback(th);
-      } finally {
-        deactivate();
-      }
-    } finally {
-      report("Executed TransactionOperation");
-    }
-  }
-
-  public void enlist(EnlistableResource enlistable) throws MulgaraTransactionException {
-    try {
-      synchronized (this) {
-        if (currentThread == null) {
-          throw new MulgaraTransactionException("Transaction not associated with thread");
-        } else if (!currentThread.equals(Thread.currentThread())) {
-          throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
-        }
-      }
-
-      if (enlisted.contains(enlistable)) {
-        return;
-      }
-
-      switch (state) {
-        case ACTUNREF:
-        case ACTREF:
-          transaction.enlistResource(enlistable.getXAResource());
-          enlisted.add(enlistable);
-          break;
-        case CONSTRUCTEDREF:
-          throw new MulgaraTransactionException("Attempt to enlist resource in uninitated ref'd transaction");
-        case CONSTRUCTEDUNREF:
-          throw new MulgaraTransactionException("Attempt to enlist resource in uninitated unref'd transaction");
-        case DEACTREF:
-          throw new MulgaraTransactionException("Attempt to enlist resource in unactivated transaction");
-        case FINISHED:
-          throw new MulgaraTransactionException("Attempt to enlist resource in finished transaction");
-        case FAILED:
-          throw new MulgaraTransactionException("Attempt to enlist resource in failed transaction");
-      }
-    } catch (Throwable th) {
-      throw implicitRollback(th);
-    }
-  }
-
-  //
-  // Used internally
-  //
-
-  private void checkActivated() throws MulgaraTransactionException {
-    synchronized (this) {
-      if (currentThread == null) {
-        throw new MulgaraTransactionException("Transaction not associated with thread");
-      } else if (!currentThread.equals(Thread.currentThread())) {
-        throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
-      }
-    }
-
-    switch (state) {
-      case ACTUNREF:
-      case ACTREF:
-        if (inuse < 0 || using < 0) {
-          throw new MulgaraTransactionException("Reference Failure, using: " + using + ", inuse: " + inuse);
-        }
-        return;
-      case CONSTRUCTEDREF:
-        throw new MulgaraTransactionException("Transaction (ref) uninitiated");
-      case CONSTRUCTEDUNREF:
-        throw new MulgaraTransactionException("Transaction (unref) uninitiated");
-      case DEACTREF:
-        throw new MulgaraTransactionException("Transaction deactivated");
-      case FINISHED:
-        throw new MulgaraTransactionException("Transaction is terminated");
-      case FAILED:
-        throw new MulgaraTransactionException("Transaction is failed", rollbackCause);
-    }
-  }
-
-  private void acquireActivationMutex() {
-    activationMutex.lock();
-  }
-
-  private void releaseActivationMutex() {
-    activationMutex.unlock();
-  }
-
-  protected void finalize() {
-    report("GC-finalize");
-    if (state != State.FINISHED && state != State.FAILED) {
-      errorReport("Finalizing incomplete transaction - aborting...", null);
-      try {
-        abortTransaction("Transaction finalized while still valid", new Throwable());
-      } catch (Throwable th) {
-        errorReport("Attempt to abort transaction from finalize failed", th);
-      }
-    }
-
-    if (state != State.FAILED && (inuse != 0 || using != 0)) {
-      errorReport("Reference counting error in transaction", null);
-    }
-
-    if (manager != null || transaction != null) {
-      errorReport("Transaction not terminated properly", null);
-    }
-  }
-
-  private void report(String desc) {
-    if (logger.isInfoEnabled()) {
-      logger.info(desc + ": " + System.identityHashCode(this) + ", state=" + state +
-          ", inuse=" + inuse + ", using=" + using);
-    }
-  }
-
-  private void debugReport(String desc) {
-    if (logger.isDebugEnabled()) {
-      logger.debug(desc + ": " + System.identityHashCode(this) + ", state=" + state +
-          ", inuse=" + inuse + ", using=" + using);
-    }
-  }
-
-  private void errorReport(String desc, Throwable cause) {
-    logger.error(desc + ": " + System.identityHashCode(this) + ", state=" + state +
-        ", inuse=" + inuse + ", using=" + using, cause != null ? cause : new Throwable());
-  }
+  public void enlist(EnlistableResource enlistable) throws MulgaraTransactionException;
 }

Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java	                        (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,249 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com). Portions created
+ * by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd.
+ * All Rights Reserved.
+ *
+ * Derivation from MulgaraTransactionManager Copyright (c) 2007 Topaz
+ * Foundation under contract by Andrae Muys (mailto:andrae at netymon.com).
+ */
+
+package org.mulgara.resolver;
+
+// Java2 packages
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+// Local packages
+import org.mulgara.query.MulgaraTransactionException;
+import org.mulgara.server.Session;
+import org.mulgara.transaction.TransactionManagerFactory;
+
+/**
+ * Manages transactions within Mulgara.
+ *
+ * see http://mulgara.org/confluence/display/dev/Transaction+Architecture
+ *
+ * Maintains association between Answer's and TransactionContext's.
+ * Manages tracking the ownership of the write-lock.
+ * Maintains the write-queue and any timeout algorithm desired.
+ * Provides new/existing TransactionContext's to DatabaseSession on request.
+ *    Note: Returns new context unless Session is currently in a User Demarcated Transaction.
+ * 
+ * @created 2006-10-06
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ *
+ * @copyright &copy;2006 <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
+ *
+ * @licence Open Software License v3.0</a>
+ */
+
+public abstract class MulgaraTransactionFactory {
+  private static final Logger logger =
+    Logger.getLogger(MulgaraTransactionFactory.class.getName());
+
+  protected final MulgaraTransactionManager manager;
+  
+  /**
+   * Contains a reference the the current writing transaction IFF it is managed
+   * by this factory.  If there is no current writing transaction, or if the
+   * writing transaction is managed by a different factory then it is null.
+   */
+  protected MulgaraTransaction writeTransaction;
+
+  private ReentrantLock mutex;
+
+  protected MulgaraTransactionFactory(MulgaraTransactionManager manager) {
+    this.manager = manager;
+    this.mutex = new ReentrantLock();
+    this.writeTransaction = null;
+  }
+
+
+  /**
+   * Obtain a transaction context associated with a DatabaseSession.
+   *
+   * Either returns the existing context if:
+   * a) we are currently within a recursive call while under implicit XA control
+   * or
+   * b) we are currently within an active user demarcated XA.
+   * otherwise creates a new transaction context and associates it with the
+   * session.
+   */
+  public abstract MulgaraTransaction getTransaction(final DatabaseSession session, boolean write)
+      throws MulgaraTransactionException;
+  
+  protected abstract Set<? extends MulgaraTransaction> getTransactionsForSession(DatabaseSession session);
+
+  /**
+   * Rollback, or abort all transactions associated with a DatabaseSession.
+   *
+   * Will only abort the transaction if the rollback attempt fails.
+   */
+  public void closingSession(DatabaseSession session) throws MulgaraTransactionException {
+    acquireMutex();
+    logger.debug("Cleaning up any stale transactions on session close");
+    try {
+      Map<MulgaraTransaction, Throwable> requiresAbort = new HashMap<MulgaraTransaction, Throwable>();
+      try {
+        Throwable error = null;
+
+        if (manager.isHoldingWriteLock(session)) {
+          logger.debug("Session holds write-lock");
+          try {
+            if (writeTransaction != null) {
+              try {
+                logger.warn("Terminating session while holding writelock:" + session + ": " + writeTransaction);
+                writeTransaction.execute(new TransactionOperation() {
+                    public void execute() throws MulgaraTransactionException {
+                      writeTransaction.heuristicRollback("Session closed while holding write lock");
+                    }
+                });
+              } catch (Throwable th) {
+                if (writeTransaction != null) {
+                  requiresAbort.put(writeTransaction, th);
+                  error = th;
+                }
+              } finally {
+                writeTransaction = null;
+              }
+            }
+          } finally {
+            manager.releaseWriteLock(session);
+          }
+        } else {
+          logger.debug("Session does not hold write-lock");
+        }
+
+        for (MulgaraTransaction transaction : getTransactionsForSession(session)) {
+          try {
+            // This is final so we can create the closure.
+            final MulgaraTransaction xa = transaction;
+            transaction.execute(new TransactionOperation() {
+                public void execute() throws MulgaraTransactionException {
+                  xa.heuristicRollback("Rollback due to session close");
+                }
+            });
+          } catch (Throwable th) {
+            requiresAbort.put(transaction, th);
+            if (error == null) {
+              error = th;
+            }
+          }
+        }
+
+        if (error != null) {
+          throw new MulgaraTransactionException("Heuristic rollback failed on session close", error);
+        }
+      } finally {
+        try {
+          abortTransactions(requiresAbort);
+        } catch (Throwable th) {
+          try {
+            logger.error("Error aborting transactions after heuristic rollback failure on session close", th);
+          } catch (Throwable throw_away) { }
+        }
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
+  /**
+   * Abort as many of the transactions as we can.
+   */
+  protected void abortTransactions(Map<MulgaraTransaction, Throwable> requiresAbort) {
+    try {
+      if (!requiresAbort.isEmpty()) {
+        // At this point the originating exception has been thrown in the caller
+        // so we attempt to ensure it doesn't get superseeded by anything that
+        // might be thrown here while logging any errors.
+        try {
+          logger.error("Heuristic Rollback Failed on session close- aborting");
+        } catch (Throwable throw_away) { } // Logging difficulties.
+
+        try {
+          for (MulgaraTransaction transaction : requiresAbort.keySet()) {
+            try {
+              transaction.abortTransaction("Heuristic Rollback failed on session close",
+                  requiresAbort.get(transaction));
+            } catch (Throwable th) {
+              try {
+                logger.error("Error aborting transaction after heuristic rollback failure on session close", th);
+              } catch (Throwable throw_away) { }
+            }
+          }
+        } catch (Throwable th) {
+          try {
+            logger.error("Loop error while aborting transactions after heuristic rollback failure on session close", th);
+          } catch (Throwable throw_away) { }
+        }
+      }
+    } catch (Throwable th) {
+      try {
+        logger.error("Unidentified error while aborting transactions after heuristic rollback failure on session close", th);
+      } catch (Throwable throw_away) { }
+    }
+  }
+
+  /**
+   * Used to replace the built in monitor to allow it to be properly released
+   * during potentially blocking operations.  All potentially blocking
+   * operations involve writes, so in these cases the write-lock is reserved
+   * allowing the mutex to be safely released and then reobtained after the
+   * blocking operation concludes.
+   */
+  protected void acquireMutex() {
+    mutex.lock();
+  }
+
+
+  protected void releaseMutex() {
+    if (!mutex.isHeldByCurrentThread()) {
+      throw new IllegalStateException("Attempt to release mutex without holding mutex");
+    }
+
+    mutex.unlock();
+  }
+
+  protected void runWithoutMutex(TransactionOperation proc) throws MulgaraTransactionException {
+    if (!mutex.isHeldByCurrentThread()) {
+      throw new IllegalStateException("Attempt to run procedure without holding mutex");
+    }
+    int holdCount = mutex.getHoldCount();
+    for (int i = 0; i < holdCount; i++) {
+      mutex.unlock();
+    }
+    try {
+      proc.execute();
+    } finally {
+      for (int i = 0; i < holdCount; i++) {
+        mutex.lock();
+      }
+    }
+  }
+}

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -13,40 +13,34 @@
  * (http://www.netymon.com, mailto:mail at netymon.com). Portions created
  * by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd.
  * All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Refactoring to focus on write-lock management contributed by Netymon
+ *   Pty Ltd on behalf of Topaz Foundation under contract.
  */
 
 package org.mulgara.resolver;
 
 // Java2 packages
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
-import javax.transaction.SystemException;
-import javax.transaction.Transaction;
-import javax.transaction.TransactionManager;
 
 // Third party packages
 import org.apache.log4j.Logger;
 
 // Local packages
 import org.mulgara.query.MulgaraTransactionException;
-import org.mulgara.server.Session;
 import org.mulgara.transaction.TransactionManagerFactory;
 
 /**
- * Manages transactions within Mulgara.
+ * Manages the Write-Lock.
  *
- * see http://mulgara.org/confluence/display/dev/Transaction+Architecture
- *
- * Maintains association between Answer's and TransactionContext's.
  * Manages tracking the ownership of the write-lock.
- * Maintains the write-queue and any timeout algorithm desired.
  * Provides new/existing TransactionContext's to DatabaseSession on request.
  *    Note: Returns new context unless Session is currently in a User Demarcated Transaction.
- * 
+ * Provides a facility to trigger a heuristic rollback of any transactions still
+ *   valid on session close.
+ * Maintains the write-queue and any timeout algorithm desired.
  *
  * @created 2006-10-06
  *
@@ -71,457 +65,97 @@
     Logger.getLogger(MulgaraTransactionManager.class.getName());
 
   // Write lock is associated with a session.
-  private Session currentWritingSession;
-  private MulgaraTransaction userTransaction;
-  private boolean autoCommit;
+  private DatabaseSession sessionHoldingWriteLock;
 
-  private ReentrantLock mutex;
-  private Condition writeLockCondition;
-  private Condition reserveCondition;
-  private Thread reservingThread;
+  // Used to support write-lock reservation.
+  private DatabaseSession sessionReservingWriteLock;
 
-  /** Set of sessions whose transactions have been rolledback.*/
-  private Set<Session> failedSessions;
+  // Used to synchronize access to other fields.
+  private final ReentrantLock mutex;
+  private final Condition writeLockCondition;
 
-  /**
-   * Map from transaction to initiating session.
-   * FIXME: This is only required for checking while we wait for 1-N.
-   *        Remove once 1-N is implemented.
-   */
-  private Map<MulgaraTransaction, Session> sessions;
+  private final MulgaraInternalTransactionFactory internalFactory;
+  private final MulgaraExternalTransactionFactory externalFactory;
 
-  /**
-   * Map from initiating session to set of transactions.
-   * Used to clean-up transactions upon session close.
-   */
-  private Map<Session, Set<MulgaraTransaction>> transactions;
-
-  /** Map of threads to active transactions. */
-  private Map<Thread, MulgaraTransaction> activeTransactions;
-
-  private final TransactionManager transactionManager;
-
   public MulgaraTransactionManager(TransactionManagerFactory transactionManagerFactory) {
-    this.currentWritingSession = null;
-    this.userTransaction = null;
-    this.autoCommit = true;
+    this.sessionHoldingWriteLock = null;
+    this.sessionReservingWriteLock = null;
     this.mutex = new ReentrantLock();
     this.writeLockCondition = this.mutex.newCondition();
-    this.reserveCondition = this.mutex.newCondition();
-    this.reservingThread = null;
 
-    this.failedSessions = new HashSet<Session>();
-    this.sessions = new HashMap<MulgaraTransaction, Session>();
-    this.transactions = new HashMap<Session, Set<MulgaraTransaction>>();
-    this.activeTransactions = new HashMap<Thread, MulgaraTransaction>();
-
-    this.transactionManager = transactionManagerFactory.newTransactionManager();
+    this.internalFactory = new MulgaraInternalTransactionFactory(this, transactionManagerFactory);
+    this.externalFactory = new MulgaraExternalTransactionFactory(this);
   }
 
-  /**
-   * Allows DatabaseSession to initiate/obtain a transaction.
-   * <ul>
-   * <li>If the Session holds the write lock, return the current Write-Transaction.</li>
-   * <li>If the Session does not hold the write lock and requests a read-only transaction,
-   *     create a new ro-transaction object and return it.</li>
-   * <li>If the Session does not hold the write lock and requests a read-write transaction,
-   *     obtain the write-lock, create a new transaction object and return it.</li>
-   * </ul>
-   */
-  public MulgaraTransaction getTransaction(DatabaseSession session, boolean write) throws MulgaraTransactionException {
-    acquireMutex();
-    try {
-      if (session == currentWritingSession) {
-        return userTransaction;
-      } 
 
-      try {
-        MulgaraTransaction transaction = write ?
-            obtainWriteLock(session) :
-            new MulgaraTransaction(this, session.newOperationContext(false));
+  MulgaraInternalTransactionFactory getInternalFactory() {
+    return internalFactory;
+  }
 
-        sessions.put(transaction, session);
-
-        if (!transactions.containsKey(session)) {
-          transactions.put(session, new HashSet<MulgaraTransaction>());
-        }
-        transactions.get(session).add(transaction);
-
-        return transaction;
-      } catch (MulgaraTransactionException em) {
-        throw em;
-      } catch (Exception e) {
-        throw new MulgaraTransactionException("Error creating transaction", e);
-      }
-    } finally {
-      releaseMutex();
-    }
+  MulgaraExternalTransactionFactory getExternalFactory() {
+    return externalFactory;
   }
 
 
   /** 
    * Obtains the write lock.
-   * Must hold readMutex on entry - but will drop readMutex if
    */
-  private MulgaraTransaction obtainWriteLock(DatabaseSession session)
-      throws MulgaraTransactionException {
-    while (writeLockHeld() || writeLockReserved()) {
-      try {
-        writeLockCondition.await();
-      } catch (InterruptedException ei) {
-        throw new MulgaraTransactionException("Interrupted while waiting for write lock", ei);
-      }
-    }
-
-    try {
-      currentWritingSession = session;
-      userTransaction = new MulgaraTransaction(this, session.newOperationContext(true));
-      return userTransaction;
-    } catch (Throwable th) {
-      releaseWriteLock();
-      throw new MulgaraTransactionException("Error while obtaining write-lock", th);
-    }
-  }
-
-  private void releaseWriteLock() {
-    // Calling this method multiple times is safe as the lock cannot be obtained
-    // between calls as this method is private, and all calling methods are
-    // synchronized.
-    currentWritingSession = null;
-    userTransaction = null;
-    writeLockCondition.signal();
-  }
-
-  public void commit(DatabaseSession session) throws MulgaraTransactionException {
+  void obtainWriteLock(DatabaseSession session) throws MulgaraTransactionException {
     acquireMutex();
     try {
-      reserveWriteLock();
-      if (failedSessions.contains(session)) {
-        throw new MulgaraTransactionException("Attempting to commit failed exception");
-      } else if (session != currentWritingSession) {
-        throw new MulgaraTransactionException(
-            "Attempting to commit while not the current writing transaction");
+      if (sessionHoldingWriteLock == session) {
+        return;
       }
 
-      setAutoCommit(session, true);
-      setAutoCommit(session, false);
-    } finally {
-      releaseMutex();
-    }
-  }
-
-
-  /**
-   * This is an explicit, user-specified rollback.
-   * 
-   * This needs to be distinguished from an implicit rollback triggered by failure.
-   */
-  public void rollback(DatabaseSession session) throws MulgaraTransactionException {
-    acquireMutex();
-    try {
-      reserveWriteLock();
-      if (session == currentWritingSession) {
+      while (writeLockHeld() || (writeLockReserved() && !writeLockReserved(session))) {
         try {
-          userTransaction.execute(new TransactionOperation() {
-            public void execute() throws MulgaraTransactionException {
-              userTransaction.explicitRollback();
-            }
-          });
-          if (userTransaction != null) {
-            // transaction referenced by something - need to explicitly end it.
-            userTransaction.abortTransaction("Rollback failed",
-                new MulgaraTransactionException("Rollback failed to terminate write transaction"));
-          }
-        } finally {
-          failedSessions.add(session);
-          releaseWriteLock();
-          setAutoCommit(session, false);
+          writeLockCondition.await();
+        } catch (InterruptedException ei) {
+          throw new MulgaraTransactionException("Interrupted while waiting for write lock", ei);
         }
-      } else if (failedSessions.contains(session)) {
-        failedSessions.remove(session);
-        setAutoCommit(session, false);
-      } else {
-        throw new MulgaraTransactionException(
-            "Attempt to rollback while not in the current writing transaction");
       }
-    } finally {
-      releaseMutex();
-    }
-  }
 
-  public void setAutoCommit(DatabaseSession session, boolean autoCommit)
-      throws MulgaraTransactionException {
-    acquireMutex();
-    try {
-      if (session == currentWritingSession && failedSessions.contains(session)) {
-        userTransaction.abortTransaction("Session failed and transaction not finalized",
-            new MulgaraTransactionException("Failed Session in setAutoCommit"));
+      if (logger.isDebugEnabled()) {
+        logger.debug("Obtaining write lock", new Throwable());
       }
-
-      if (session == currentWritingSession || failedSessions.contains(session)) {
-        if (autoCommit) {
-          // AutoCommit off -> on === branch on current state of transaction.
-          if (session == currentWritingSession) {
-            // Within active transaction - commit and finalise.
-            try {
-              runWithoutMutex(new TransactionOperation() {
-                public void execute() throws MulgaraTransactionException {
-                  userTransaction.execute(new TransactionOperation() {
-                    public void execute() throws MulgaraTransactionException {
-                      userTransaction.dereference();
-                      userTransaction.commitTransaction();
-                    }
-                  });
-                }
-              });
-            } finally {
-              releaseWriteLock();
-              this.autoCommit = true;
-            }
-          } else if (failedSessions.contains(session)) {
-            // Within failed transaction - cleanup.
-            failedSessions.remove(session);
-          }
-        } else {
-          logger.info("Attempt to set autocommit false twice");
-          // AutoCommit off -> off === no-op. Log info.
-        }
-      } else {
-        if (autoCommit) {
-          // AutoCommit on -> on === no-op.  Log info.
-          logger.info("Attempting to set autocommit true without setting it false");
-        } else {
-          // AutoCommit on -> off == Start new transaction.
-          userTransaction = getTransaction(session, true);
-          userTransaction.reference();
-          this.autoCommit = false;
-        }
-      }
+      sessionHoldingWriteLock = session;
     } finally {
       releaseMutex();
     }
   }
 
-  public void rollbackCurrentTransactions(Session session) throws MulgaraTransactionException {
+  boolean isHoldingWriteLock(DatabaseSession session) {
     acquireMutex();
     try {
-      try {
-        if (failedSessions.contains(session)) {
-          failedSessions.remove(session);
-          return;
-        }
-
-        Throwable error = null;
-
-        try {
-          if (session == currentWritingSession) {
-            logger.warn("Terminating session while holding writelock:" + session + ": " + userTransaction);
-            userTransaction.execute(new TransactionOperation() {
-                public void execute() throws MulgaraTransactionException {
-                  throw new MulgaraTransactionException("Terminating session while holding writelock");
-                }
-            });
-          }
-        } catch (Throwable th) {
-          error = th;
-        }
-
-        if (transactions.containsKey(session)) {
-          for (MulgaraTransaction transaction : transactions.get(session)) {
-            try {
-              transaction.execute(new TransactionOperation() {
-                public void execute() throws MulgaraTransactionException {
-                  throw new MulgaraTransactionException("Rolling back transactions due to session close");
-                }
-              });
-            } catch (MulgaraTransactionException em) {
-              // ignore.
-            } catch (Throwable th) {
-              if (error == null) {
-                error = th;
-              }
-            }
-          }
-        }
-
-        if (error != null) {
-          if (error instanceof MulgaraTransactionException) {
-            throw (MulgaraTransactionException)error;
-          } else {
-            throw new MulgaraTransactionException("Error in rollback on session close", error);
-          }
-        }
-      } finally {
-        if (transactions.containsKey(session)) {
-          logger.error("Error in transaction rollback due to session close - aborting");
-          abortCurrentTransactions(session);
-        }
-      }
+      return sessionHoldingWriteLock == session;
     } finally {
       releaseMutex();
     }
   }
 
-  private void abortCurrentTransactions(Session session) throws MulgaraTransactionException {
-    acquireMutex();
-    try {
-      try {
-        Throwable error = null;
-        for (MulgaraTransaction transaction : transactions.get(session)) {
-          try {
-            transaction.abortTransaction("Transaction still valid on session close", new Throwable());
-          } catch (Throwable th) {
-            try {
-              if (error == null) {
-                error = th;
-              }
-            } catch (Throwable throw_away) {}
-          }
-        }
 
-        if (error != null) {
-          if (error instanceof MulgaraTransactionException) {
-            throw (MulgaraTransactionException)error;
-          } else {
-            throw new MulgaraTransactionException("Error in rollback on session close", error);
-          }
-        }
-      } finally {
-        if (session == currentWritingSession) {
-          logger.error("Failed to abort write-transaction on session close - Server restart required");
-        }
-      }
-    } finally {
-      releaseMutex();
-    }
-  }
-
-  //
-  // Transaction livecycle callbacks.
-  //
-
-  public Transaction transactionStart(MulgaraTransaction transaction) throws MulgaraTransactionException {
+  void releaseWriteLock(DatabaseSession session) throws MulgaraTransactionException {
     acquireMutex();
     try {
-      try {
-        logger.info("Beginning Transaction");
-        if (activeTransactions.get(Thread.currentThread()) != null) {
-          throw new MulgaraTransactionException(
-              "Attempt to start transaction in thread with exiting active transaction.");
-        } else if (activeTransactions.containsValue(transaction)) {
-          throw new MulgaraTransactionException("Attempt to start transaction twice");
-        }
-
-        transactionManager.begin();
-        Transaction jtaTrans = transactionManager.getTransaction();
-
-        activeTransactions.put(Thread.currentThread(), transaction);
-
-        return jtaTrans;
-      } catch (Exception e) {
-        throw new MulgaraTransactionException("Transaction Begin Failed", e);
+      if (sessionHoldingWriteLock == null) {
+        return;
       }
-    } finally {
-      releaseMutex();
-    }
-  }
-
-  public void transactionResumed(MulgaraTransaction transaction, Transaction jtaXA) 
-      throws MulgaraTransactionException {
-    acquireMutex();
-    try {
-      if (activeTransactions.get(Thread.currentThread()) != null) {
-        throw new MulgaraTransactionException(
-            "Attempt to resume transaction in already activated thread");
-      } else if (activeTransactions.containsValue(transaction)) {
-        throw new MulgaraTransactionException("Attempt to resume active transaction");
+      if (sessionHoldingWriteLock != session) {
+        throw new MulgaraTransactionException("Attempted to release write lock being held by another session");
       }
-      
-      try {
-        transactionManager.resume(jtaXA);
-        activeTransactions.put(Thread.currentThread(), transaction);
-      } catch (Exception e) {
-        throw new MulgaraTransactionException("Resume Failed", e);
+      if (logger.isDebugEnabled()) {
+        logger.debug("Releasing writelock", new Throwable());
       }
+      sessionHoldingWriteLock = null;
+      writeLockCondition.signal();
     } finally {
       releaseMutex();
     }
   }
 
-  public Transaction transactionSuspended(MulgaraTransaction transaction)
-      throws MulgaraTransactionException {
-    acquireMutex();
-    try {
-      try {
-        if (transaction != activeTransactions.get(Thread.currentThread())) {
-          throw new MulgaraTransactionException(
-              "Attempt to suspend transaction from outside thread");
-        }
 
-        if (autoCommit && transaction == userTransaction) {
-          logger.error("Attempt to suspend write transaction without setting AutoCommit Off");
-          throw new MulgaraTransactionException(
-              "Attempt to suspend write transaction without setting AutoCommit Off");
-        }
-
-        Transaction xa = transactionManager.suspend();
-        activeTransactions.remove(Thread.currentThread());
-
-        return xa;
-      } catch (Throwable th) {
-        logger.error("Attempt to suspend failed", th);
-        try {
-          transactionManager.setRollbackOnly();
-        } catch (Throwable t) {
-          logger.error("Attempt to setRollbackOnly() failed", t);
-        }
-        throw new MulgaraTransactionException("Suspend failed", th);
-      }
-    } finally {
-      releaseMutex();
-    }
-  }
-
-  public void transactionComplete(MulgaraTransaction transaction) {
-    acquireMutex();
-    try {
-      if (transaction == userTransaction) {
-        releaseWriteLock();
-      }
-
-      activeTransactions.remove(Thread.currentThread());
-      Session session = (Session)sessions.get(transaction);
-      sessions.remove(transaction);
-      transactions.remove(session);
-    } finally {
-      releaseMutex();
-    }
-  }
-
-  public void transactionAborted(MulgaraTransaction transaction) {
-    acquireMutex();
-    try {
-      try {
-        // Make sure this cleans up the transaction metadata - this transaction is DEAD!
-        if (transaction == userTransaction) {
-          failedSessions.add(currentWritingSession);
-        }
-        transactionComplete(transaction);
-      } catch (Throwable th) {
-        // FIXME: This should probably abort the entire server after logging the error!
-        logger.error("Error managing transaction abort", th);
-      }
-    } finally {
-      releaseMutex();
-    }
-  }
-
   public void setTransactionTimeout(int transactionTimeout) {
-    try {
-      transactionManager.setTransactionTimeout(transactionTimeout);
-    } catch (SystemException es) {
-      logger.warn("Unable to set transaction timeout: " + transactionTimeout, es);
-    }
+    internalFactory.setTransactionTimeout(transactionTimeout);
   }
 
   /**
@@ -538,33 +172,51 @@
 
   /**
    * Used to reserve the write lock during a commit or rollback.
+   * Should only be used by a transaction manager.
    */
-  private void reserveWriteLock() throws MulgaraTransactionException {
-    if (!mutex.isHeldByCurrentThread()) {
-      throw new IllegalStateException("Attempt to set modify without holding mutex");
-    }
+  void reserveWriteLock(DatabaseSession session) throws MulgaraTransactionException {
+    acquireMutex();
+    try {
+      if (session != sessionReservingWriteLock && session != sessionHoldingWriteLock) {
+        throw new IllegalStateException("Attempt to reserve writelock without holding writelock");
+      }
+      if (session != sessionReservingWriteLock && sessionReservingWriteLock != null) {
+        throw new IllegalStateException("Attempt to reserve writelock when writelock already reserved");
+      }
 
-    if (Thread.currentThread().equals(reservingThread)) {
-      return;
+      sessionReservingWriteLock = session;
+    } finally {
+      releaseMutex();
     }
+  }
 
-    while (reservingThread != null) {
-      try {
-        reserveCondition.await();
-      } catch (InterruptedException ei) {
-        throw new MulgaraTransactionException("Thread interrupted while reserving write lock", ei);
-      }
-    }
-    reservingThread = Thread.currentThread();
+  boolean writeLockReserved() {
+    return sessionReservingWriteLock != null;
   }
 
-  private boolean writeLockReserved() {
-    // TRUE iff there is a reserving thread AND it is different thread to the current thread.
-    return reservingThread != null && !Thread.currentThread().equals(reservingThread);
+  boolean writeLockReserved(DatabaseSession session) {
+    return session == sessionReservingWriteLock;
   }
 
+  void releaseReserve(DatabaseSession session) {
+    acquireMutex();
+    try {
+      if (!writeLockReserved()) {
+        return;
+      }
+      if (!writeLockReserved(session)) {
+        throw new IllegalStateException("Attempt to release reserve without holding reserve");
+      }
+
+      sessionReservingWriteLock = null;
+      writeLockCondition.signal();
+    } finally {
+      releaseMutex();
+    }
+  }
+
   private boolean writeLockHeld() {
-    return currentWritingSession != null;
+    return sessionHoldingWriteLock != null;
   }
 
   private void releaseMutex() {
@@ -572,28 +224,59 @@
       throw new IllegalStateException("Attempt to release mutex without holding mutex");
     }
 
-    if (mutex.getHoldCount() == 1 && Thread.currentThread().equals(reservingThread)) {
-      reservingThread = null;
-      reserveCondition.signal();
-    }
-
     mutex.unlock();
   }
 
-  private void runWithoutMutex(TransactionOperation proc) throws MulgaraTransactionException {
-    if (!mutex.isHeldByCurrentThread()) {
-      throw new IllegalStateException("Attempt to run procedure without holding mutex");
+  public void closingSession(DatabaseSession session) throws MulgaraTransactionException {
+    // Calls to final fields do not require mutex.  As both of these calls
+    // potentially call back into the manager, calling these while holding the
+    // lock can invalidate lock-ordering and result in a deadlock.
+    Throwable error = null;
+    try {
+      internalFactory.closingSession(session);
+    } catch (Throwable th) {
+      logger.error("Error signalling session-close to internal xa-factory", th);
+      error = (error == null) ? th : error;
     }
-    int holdCount = mutex.getHoldCount();
-    for (int i = 0; i < holdCount; i++) {
-      mutex.unlock();
+
+    try {
+      externalFactory.closingSession(session);
+    } catch (Throwable th) {
+      logger.error("Error signalling session-close to external xa-factory", th);
+      error = (error == null) ? th : error;
     }
+
+    // This code should not be required, but is there to ensure the manager is
+    // reset regardless of errors in the factories.
+    acquireMutex();
     try {
-      proc.execute();
-    } finally {
-      for (int i = 0; i < holdCount; i++) {
-        mutex.lock();
+      if (writeLockReserved(session)) {
+        try {
+          releaseReserve(session);
+        } catch (Throwable th) {
+          logger.error("Error releasing reserve on force-close", th);
+          error = (error == null) ? th : error;
+        }
       }
+
+      if (isHoldingWriteLock(session)) {
+        try {
+          releaseWriteLock(session);
+        } catch (Throwable th) {
+          logger.error("Error releasing write-lock on force-close", th);
+          error = (error == null) ? th : error;
+        }
+      }
+
+      if (error != null) {
+        if (error instanceof MulgaraTransactionException) {
+          throw (MulgaraTransactionException)error;
+        } else {
+          throw new MulgaraTransactionException("Error force releasing write-lock", error);
+        }
+      }
+    } finally {
+      releaseMutex();
     }
   }
 }

Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java	                        (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,536 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com) under contract to 
+ * Topaz Foundation. Portions created under this contract are
+ * Copyright (c) 2007 Topaz Foundation
+ * All Rights Reserved.
+ */
+
+package org.mulgara.resolver;
+
+// Java2 packages
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+// Local packages
+import org.mulgara.query.MulgaraTransactionException;
+import org.mulgara.server.ResourceManagerInstanceAdaptor;
+import org.mulgara.util.Assoc1toNMap;
+
+/**
+ * Provides an external JTA-compliant TransactionManager with the ability to
+ * control Mulgara Transactions.
+ *
+ * @created 2007-11-07
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ *
+ * @copyright &copy;2006 <a href="http://www.topazproject.org/">Topaz Project</a>
+ *
+ * @licence Open Software License v3.0</a>
+ */
+
+public class MulgaraXAResourceContext {
+  private static final Logger logger =
+    Logger.getLogger(MulgaraXAResourceContext.class.getName());
+  /**
+   * Map from keyed from the {@link Integer} value of the various flags
+   * defined in {@link XAResource} and mapping to the formatted name for that
+   * flag.
+   */
+  private final static Map<Integer, String> flagMap = new HashMap<Integer, String>();
+
+  static {
+    flagMap.put(XAResource.TMENDRSCAN,   "TMENDRSCAN");
+    flagMap.put(XAResource.TMFAIL,       "TMFAIL");
+    flagMap.put(XAResource.TMJOIN,       "TMJOIN");
+    flagMap.put(XAResource.TMONEPHASE,   "TMONEPHASE");
+    flagMap.put(XAResource.TMRESUME,     "TMRESUME");
+    flagMap.put(XAResource.TMSTARTRSCAN, "TMSTARTRSCAN");
+    flagMap.put(XAResource.TMSUCCESS,    "TMSUCCESS");
+    flagMap.put(XAResource.TMSUSPEND,    "TMSUSPEND");
+  }
+
+  private final MulgaraExternalTransactionFactory factory;
+
+  protected final DatabaseSession session;
+
+  private final Assoc1toNMap<MulgaraExternalTransaction, Xid> xa2xid;
+
+  private final ReentrantLock mutex;
+
+  private final UUID uniqueId;
+
+  MulgaraXAResourceContext(MulgaraExternalTransactionFactory factory, DatabaseSession session) {
+    logger.info("Creating MulgaraXAResource");
+    this.factory = factory;
+    this.session = session;
+    this.xa2xid = new Assoc1toNMap<MulgaraExternalTransaction, Xid>();
+    this.mutex = new ReentrantLock();
+    this.uniqueId = UUID.randomUUID();
+  }
+
+  public XAResource getResource(boolean writing) {
+    return new MulgaraXAResource(writing);
+  }
+
+  private class MulgaraXAResource implements XAResource,
+      ResourceManagerInstanceAdaptor {
+    private final boolean writing;
+
+    public MulgaraXAResource(boolean writing) {
+      this.writing = writing;
+    }
+
+    /**
+     * Commit transaction identified by xid.
+     *
+     * Transaction must be Idle, Prepared, or Heuristically-Completed.
+     * If transaction not Heuristically-Completed we are required to finish it,
+     * clean up, and forget it.
+     * If transaction is Heuristically-Completed we throw an exception and wait
+     * for a call to forget().
+     */
+    public void commit(Xid xid, boolean onePhase) throws XAException {
+      acquireMutex();
+      try {
+        xid = convertXid(xid);
+        logger.info("Performing commit: " + parseXid(xid));
+        MulgaraExternalTransaction xa = xa2xid.get1(xid);
+        if (xa == null) {
+          throw new XAException(XAException.XAER_NOTA);
+        } else if (xa.isHeuristicallyRollbacked()) {
+          // HEURRB causes difficulties with JOTM - so throw the less precise
+          // but still correct RBROLLBACK.
+          // Note: Found the problem here - The J2EE Connector Architecture
+          // 7.6.2.2 requires an XA_RB* exception in the case of 1PC and 7.6.2.5
+          // implies that HEURRB is not permitted during 2PC - this seems broken
+          // to me, but that's the spec.
+//          throw new XAException(XAException.XA_HEURRB);
+          throw new XAException(XAException.XA_RBROLLBACK);
+        } else if (xa.isHeuristicallyCommitted()) {
+          throw new XAException(XAException.XA_HEURCOM);
+        }
+
+        if (onePhase) {
+          try {
+            xa.prepare(xid);
+          } catch (XAException ex) {
+            if (ex.errorCode != XAException.XA_RDONLY) {
+              doRollback(xa, xid);
+            }
+            // Note: XA spec requires us to forget about transactions that fail
+            // during commit.  doRollback throws exception under Heuristic
+            // Completion - when we do want to remember transaction.
+            xa2xid.remove1(xa);
+            throw ex;
+          }
+        }
+
+        try {
+          xa.commit(xid);
+          xa2xid.remove1(xa);
+        } catch (XAException ex) {
+          // We are not allowed to forget this transaction if we completed
+          // heuristically.
+          switch (ex.errorCode) { 
+            case XAException.XA_HEURHAZ:
+            case XAException.XA_HEURCOM:
+            case XAException.XA_HEURRB:
+            case XAException.XA_HEURMIX:
+              throw ex;
+            default:
+              xa2xid.remove1(xa);
+              throw ex;
+          }
+        }
+      } finally {
+        releaseMutex();
+      }
+    }
+
+    /**
+     * Deactivate a transaction.
+     *
+     * TMSUCCESS: Move to Idle and await call to rollback, prepare, or commit.
+     * TMFAIL: Move to RollbackOnly; await call to rollback.
+     * TMSUSPEND: Move to Idle and await start(TMRESUME) or end(TMSUCCESS|FAIL)
+     *
+     * In all cases disassociate from current session.
+     */
+    public void end(Xid xid, int flags) throws XAException {
+      acquireMutex();
+      try {
+        xid = convertXid(xid);
+        logger.info("Performing end(" + formatFlags(flags) + "): " + parseXid(xid));
+        MulgaraExternalTransaction xa = xa2xid.get1(xid);
+        if (xa == null) {
+          throw new XAException(XAException.XAER_NOTA);
+        }
+        switch (flags) {
+          case TMFAIL:
+            doRollback(xa, xid);
+            break;
+          case TMSUCCESS:
+            if (xa.isHeuristicallyRollbacked()) {
+              throw new XAException(XAException.XA_RBPROTO);
+            }
+            break;
+          case TMSUSPEND: // Should I be tracking the xid's state to ensure
+                          // conformance with the X/Open state diagrams?
+            break;
+          default:
+            logger.error("Invalid flag passed to end() : " + flags);
+            throw new XAException(XAException.XAER_INVAL);
+        }
+
+        try {
+          // If XA is currently associated with session, disassociate it.
+          factory.disassociateTransaction(session, xa);
+        } catch (MulgaraTransactionException em) {
+          logger.error("Error disassociating transaction from session", em);
+          throw new XAException(XAException.XAER_PROTO);
+        }
+      } finally {
+        releaseMutex();
+      }
+    }
+
+    public void forget(Xid xid) throws XAException {
+      acquireMutex();
+      try {
+        xid = convertXid(xid);
+        logger.info("Performing forget: " + parseXid(xid));
+        MulgaraExternalTransaction xa = xa2xid.get1(xid);
+        if (xa == null) {
+          throw new XAException(XAException.XAER_NOTA);
+        }
+        try {
+          if (!xa.isHeuristicallyRollbacked()) {
+            try {
+              xa.abortTransaction("External XA Manager specified 'forget'", new Throwable());
+            } catch (MulgaraTransactionException em) {
+              logger.error("Failed to abort transaction in forget", em);
+              throw new XAException(XAException.XAER_RMERR);
+            }
+          }
+        } finally {
+          xa2xid.remove1(xa);
+        }
+      } finally {
+        releaseMutex();
+      }
+    }
+
+    public int getTransactionTimeout() {
+      acquireMutex();
+      try {
+        logger.info("Performing getTransactionTimeout");
+        return 3600;
+      } finally {
+        releaseMutex();
+      }
+    }
+
+    public boolean isSameRM(XAResource xares) {
+      acquireMutex();
+      try {
+        logger.info("Performing isSameRM");
+        if (!xares.getClass().equals(MulgaraXAResource.class)) {
+          return false;
+        } else {
+          // Based on X/Open-XA-TP section 3.2 I believe a 'Resource Manager
+          // Instance' corresponds to a session, as each session 'supports
+          // independent transaction completion'.
+          return session == ((MulgaraXAResource)xares).getSession();
+        }
+      } finally {
+        releaseMutex();
+      }
+    }
+
+
+    public int prepare(Xid xid) throws XAException {
+      acquireMutex();
+      try {
+        xid = convertXid(xid);
+        logger.info("Performing prepare: " + parseXid(xid));
+        MulgaraExternalTransaction xa = xa2xid.get1(xid);
+        if (xa == null) {
+          throw new XAException(XAException.XAER_NOTA);
+        } else if (xa.isRollbacked()) {
+          throw new XAException(XAException.XA_RBROLLBACK);
+        }
+
+        xa.prepare(xid);
+
+        return XA_OK;
+      } finally {
+        releaseMutex();
+      }
+    }
+
+    /**
+     * We don't currently support recover.
+     * FIXME: We should at least handle the case where we are asked to recover
+     * when we haven't crashed.
+     */
+    public Xid[] recover(int flag) {
+      acquireMutex();
+      try {
+        logger.info("Performing recover");
+        return new Xid[] {};
+      } finally {
+        releaseMutex();
+      }
+    }
+
+
+    public void rollback(Xid xid) throws XAException {
+      acquireMutex();
+      try {
+        xid = convertXid(xid);
+        logger.info("Performing rollback: " + parseXid(xid));
+        MulgaraExternalTransaction xa = xa2xid.get1(xid);
+        if (xa == null) {
+          throw new XAException(XAException.XAER_NOTA);
+        }
+
+        doRollback(xa, xid);
+        // If we don't throw a Heuristic Exception we need to forget this
+        // transaction.  doRollback only throws Heuristic Exceptions.
+        xa2xid.remove1(xa);
+      } finally {
+        releaseMutex();
+      }
+    }
+
+
+    /**
+     * Performs rollback.  Only throws exception if transaction is subject to
+     * Heuristic Completion.
+     */
+    private void doRollback(MulgaraExternalTransaction xa, Xid xid) throws XAException {
+      if (xa.isHeuristicallyRollbacked()) {
+        logger.warn("Attempted to rollback heuristically rollbacked transaction: " + xa.getHeuristicCode());
+        throw new XAException(xa.getHeuristicCode());
+      } else if (!xa.isRollbacked()) {
+        xa.rollback(xid);
+      }
+    }
+
+
+    public boolean setTransactionTimeout(int seconds) {
+      acquireMutex();
+      try {
+        logger.info("Performing setTransactionTimeout");
+        return false;
+      } finally {
+        releaseMutex();
+      }
+    }
+
+
+    public void start(Xid xid, int flags) throws XAException {
+      acquireMutex();
+      try {
+        xid = convertXid(xid);
+        logger.info("Performing start(" + formatFlags(flags) + "): " + parseXid(xid));
+        switch (flags) {
+          case TMNOFLAGS:
+            if (xa2xid.containsN(xid)) {
+              throw new XAException(XAException.XAER_DUPID);
+            } else if (factory.hasAssociatedTransaction(session)) {
+              throw new XAException(XAException.XA_RBDEADLOCK);
+            } else {
+              // FIXME: Need to consider read-only transactions here.
+              try {
+                MulgaraExternalTransaction xa = factory.createTransaction(session, xid, writing);
+                xa2xid.put(xa, xid);
+              } catch (MulgaraTransactionException em) {
+                logger.error("Failed to create transaction", em);
+                throw new XAException(XAException.XAER_RMFAIL);
+              }
+            }
+            break;
+          case TMJOIN:
+            if (!factory.hasAssociatedTransaction(session)) {
+              throw new XAException(XAException.XAER_NOTA);
+            } else if (!factory.getAssociatedTransaction(session).getXid().equals(xid)) {
+              throw new XAException(XAException.XAER_OUTSIDE);
+            }
+            break;
+          case TMRESUME:
+            MulgaraExternalTransaction xa = xa2xid.get1(xid);
+            if (xa == null) {
+              throw new XAException(XAException.XAER_NOTA);
+            } else if (xa.isRollbacked()) {
+              throw new XAException(XAException.XA_RBROLLBACK);
+            } else {
+              if (!factory.associateTransaction(session, xa)) {
+                // session already associated with a transaction.
+                throw new XAException(XAException.XAER_PROTO);
+              }
+            }
+            break;
+        }
+      } finally {
+        releaseMutex();
+      }
+
+    }
+
+    public Serializable getRMId() {
+      return uniqueId;
+    }
+
+    /**
+     * Required only because Java has trouble with accessing fields from
+     * inner-classes.
+     */
+    private DatabaseSession getSession() { return session; }
+  }
+  
+  /**
+   * Used to replace the built in monitor to allow it to be properly released
+   * during potentially blocking operations.  All potentially blocking
+   * operations involve writes, so in these cases the write-lock is reserved
+   * allowing the mutex to be safely released and then reobtained after the
+   * blocking operation concludes.
+   */
+  protected void acquireMutex() {
+    mutex.lock();
+  }
+
+
+  protected void releaseMutex() {
+    if (!mutex.isHeldByCurrentThread()) {
+      throw new IllegalStateException("Attempt to release mutex without holding mutex");
+    }
+
+    mutex.unlock();
+  }
+
+  public static String parseXid(Xid xid) {
+    return xid.toString();
+  }
+
+  private static InternalXid convertXid(Xid xid) {
+    return new InternalXid(xid);
+  }
+
+  /**
+   * Provides an Xid that compares equal by value.
+   */
+  private static class InternalXid implements Xid {
+    private byte[] bq;
+    private int fi;
+    private byte[] gtid;
+
+    public InternalXid(Xid xid) {
+      byte[] tbq = xid.getBranchQualifier();
+      byte[] tgtid = xid.getGlobalTransactionId();
+      this.bq = new byte[tbq.length];
+      this.fi = xid.getFormatId();
+      this.gtid = new byte[tgtid.length];
+      System.arraycopy(tbq, 0, this.bq, 0, tbq.length);
+      System.arraycopy(tgtid, 0, this.gtid, 0, tgtid.length);
+    }
+
+    public byte[] getBranchQualifier() {
+      return bq;
+    }
+
+    public int getFormatId() {
+      return fi;
+    }
+
+    public byte[] getGlobalTransactionId() {
+      return gtid;
+    }
+
+    public int hashCode() {
+      return Arrays.hashCode(bq) ^ fi ^ Arrays.hashCode(gtid);
+    }
+
+    public boolean equals(Object rhs) {
+      if (!(rhs instanceof InternalXid)) {
+        return false;
+      } else {
+        InternalXid rhx = (InternalXid)rhs;
+        return this.fi == rhx.fi &&
+            Arrays.equals(this.bq, rhx.bq) &&
+            Arrays.equals(this.gtid, rhx.gtid);
+      }
+    }
+
+    public String toString() {
+      return ":" + fi + ":" + Arrays.hashCode(gtid) + ":" + Arrays.hashCode(bq) + ":";
+    }
+  }
+
+  /**
+   * Format bitmasks defined by {@link XAResource}.
+   *
+   * @param flags  a bitmask composed from the constants defined in
+   *   {@link XAResource}
+   * @return a formatted representation of the <var>flags</var>
+   */
+  private static String formatFlags(int flags)
+  {
+    // Short-circuit evaluation if we've been explicitly passed no flags
+    if (flags == XAResource.TMNOFLAGS) {
+      return "TMNOFLAGS";
+    }
+
+    StringBuffer buffer = new StringBuffer();
+
+    // Add any flags that are present
+    for (Map.Entry<Integer, String> entry : flagMap.entrySet()) {
+      int flag = entry.getKey();
+
+      // If this flag is present, add it to the formatted output and remove
+      // from the bitmask
+      if ((flag & flags) == flag) {
+        if (buffer.length() > 0) {
+          buffer.append("|");
+        }
+        buffer.append(entry.getValue());
+        flags &= ~flag;
+      }
+    }
+
+    // We would expect to have removed all flags by this point
+    // If there's some unknown flag we've missed, format it as hexadecimal
+    if (flags != 0) {
+      if (buffer.length() > 0) {
+        buffer.append(",");
+      }
+      buffer.append("0x").append(Integer.toHexString(flags));
+    }
+
+    return buffer.toString();
+  }
+}

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/XADatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/XADatabaseSessionUnitTest.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/XADatabaseSessionUnitTest.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -611,7 +611,7 @@
 
         // Evaluate the query
         Answer answer = new ArrayAnswer(session.query(new Query(
-          (List<Object>)(List)test.selectList,          // SELECT
+          test.selectList,          // SELECT
           test.model,               // FROM
           test.query,               // WHERE
           null,                     // HAVING

Modified: trunk/src/jar/resolver-distributed/java/org/mulgara/resolver/distributed/NetworkDelegator.java
===================================================================
--- trunk/src/jar/resolver-distributed/java/org/mulgara/resolver/distributed/NetworkDelegator.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-distributed/java/org/mulgara/resolver/distributed/NetworkDelegator.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -198,7 +198,7 @@
     );
 
     // convert the variable set to a variable list - add types via unchecked casts
-    List<Object> variables = new ArrayList<Object>((Set<Variable>)globalConstraint.getVariables());
+    List<Variable> variables = new ArrayList<Variable>((Set<Variable>)globalConstraint.getVariables());
     // build the new query
     return new Query(variables, new ModelResource(model.getURI()), globalConstraint, null, Collections.EMPTY_LIST, null, 0, new UnconstrainedAnswer());
   }

Modified: trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolver.java
===================================================================
--- trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolver.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolver.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
  * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
  * Plugged In Software Pty Ltd. All Rights Reserved.
  *
- * Contributor(s): N/A.
+ * Contributor(s):
+ *    Migration to AbstractXAResource copyright 2008 The Topaz Foundation
  *
  * [NOTE: The text of this Exhibit A may differ slightly from the text
  * of the notices in the Source Code files of the Original Code. You
@@ -84,8 +85,10 @@
    */
   private final Set<Stating> statingSet;
 
-  private XAResolverSession xaResolverSession;
+  private final XAResource xares;
 
+  private final XAResolverSession xaResolverSession;
+
   //
   // Constructors
   //
@@ -105,34 +108,30 @@
    *   is {@link NodePool#NONE}
    */
   MemoryResolver(ResolverSession resolverSession,
-                 Resolver        systemResolver,
                  long            rdfType,
-                 long            systemModel,
                  URI             modelTypeURI,
                  Set<Stating>    statingSet)
       throws ResolverFactoryException {
-
-    // Validate "modelType" parameter
-    if (modelTypeURI == null) {
-      throw new IllegalArgumentException("Model type can't be null");
-    }
-
-    // Initialize fields
-    memoryModelTypeURI = modelTypeURI;
-    this.rdfType = rdfType;
-    this.resolverSession = resolverSession;
-    this.statingSet = statingSet;
-    this.xaResolverSession = null;
+    this(resolverSession, rdfType, modelTypeURI, statingSet, null, null);
   }
 
-
   MemoryResolver(long              rdfType,
-                 long              systemModel,
                  URI               modelTypeURI,
                  Set<Stating>      statingSet,
-                 XAResolverSession resolverSession)
+                 XAResolverSession resolverSession,
+                 ResolverFactory   resolverFactory)
       throws ResolverFactoryException {
-    
+    this(resolverSession, rdfType, modelTypeURI, statingSet, resolverSession,
+         resolverFactory);
+  }
+
+  private MemoryResolver(ResolverSession   resolverSession,
+                         long              rdfType,
+                         URI               modelTypeURI,
+                         Set<Stating>      statingSet,
+                         XAResolverSession xaResolverSession,
+                         ResolverFactory   resolverFactory)
+      throws ResolverFactoryException {
     // Validate "modelType" parameter
     if (modelTypeURI == null) {
       throw new IllegalArgumentException("Model type can't be null");
@@ -143,20 +142,21 @@
     this.rdfType = rdfType;
     this.resolverSession = resolverSession;
     this.statingSet = statingSet;
-    this.xaResolverSession = resolverSession;
+    this.xaResolverSession = xaResolverSession;
+
+    this.xares = (xaResolverSession != null) ?
+          new MemoryXAResource(10, xaResolverSession, resolverFactory) :
+          new DummyXAResource(10);
   }
 
 
+
   //
   // Methods implementing Resolver
   //
 
   public XAResource getXAResource() {
-    if (xaResolverSession != null) {
-      return new MemoryXAResource(10, xaResolverSession);
-    } else {
-      return new DummyXAResource(10);
-    }
+    return xares;
   }
 
 

Modified: trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolverFactory.java
===================================================================
--- trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolverFactory.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolverFactory.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -74,11 +74,6 @@
 
 
   /**
-   * The preallocated local node representing the system model (<code>#</code>).
-   */
-  private long systemModel;
-
-  /**
    * The {@link Stating}s which occur in all models created by resolvers
    * created by this factory.
    */
@@ -105,8 +100,6 @@
     rdfType = initializer.preallocate(new URIReferenceImpl(RDF.TYPE));
     initializer.preallocate(new URIReferenceImpl(modelTypeURI));
 
-    systemModel = initializer.getSystemModel();
-
     // Claim mulgara:MemoryModel
     initializer.addModelType(modelTypeURI, this);
 
@@ -136,7 +129,6 @@
 
   public void setDatabaseMetadata(DatabaseMetadata metadata) {
     rdfType = metadata.getRdfTypeNode();
-    systemModel = metadata.getSystemModelNode();
   }
 
 
@@ -222,9 +214,7 @@
   ) throws ResolverFactoryException {
     if (logger.isDebugEnabled()) logger.debug("Creating memory resolver");
     return new MemoryResolver(resolverSession,
-                              systemResolver,
                               rdfType,
-                              systemModel,
                               modelTypeURI,
                               statingSet);
   }
@@ -234,8 +224,9 @@
     assert sessionFactory != null;
     if (logger.isDebugEnabled()) logger.debug("Creating memory resolver factory");
     try {
-      return new MemoryResolver(rdfType, systemModel, modelTypeURI, statingSet,
-                                (XAResolverSession) sessionFactory.newWritableResolverSession());
+      return new MemoryResolver(rdfType, modelTypeURI, statingSet,
+                                (XAResolverSession) sessionFactory.newWritableResolverSession(),
+                                this);
     } catch (ResolverSessionFactoryException er) {
       throw new ResolverFactoryException("Failed to obtain a new ResolverSession", er);
     }

Modified: trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryXAResource.java
===================================================================
--- trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryXAResource.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryXAResource.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
  * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
  * Plugged In Software Pty Ltd. All Rights Reserved.
  *
- * Contributor(s): N/A.
+ * Contributor(s):
+ *    Migration to AbstractXAResource copyright 2008 The Topaz Foundation
  *
  * [NOTE: The text of this Exhibit A may differ slightly from the text
  * of the notices in the Source Code files of the Original Code. You
@@ -27,23 +28,15 @@
 
 package org.mulgara.resolver.memory;
 
-// Java 2 standard packages
-import java.util.*;
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
-
-// Third party packages
-import org.apache.log4j.Logger;
-
-
+import org.mulgara.resolver.spi.AbstractXAResource;
+import org.mulgara.resolver.spi.AbstractXAResource.RMInfo;
+import org.mulgara.resolver.spi.AbstractXAResource.TxInfo;
+import org.mulgara.resolver.spi.ResolverFactory;
 import org.mulgara.store.xa.SimpleXAResource;
-import org.mulgara.store.xa.SimpleXAResourceException;
 import org.mulgara.store.xa.XAResolverSession;
 
 /**
- * A dummy implementation of the {@link XAResource} interface which logs the
- * calls made to it, but otherwise ignores them.
+ * Implements the XAResource for the {@link MemoryResolver}.
  *
  * @created 2004-05-12
  * @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a>
@@ -54,38 +47,8 @@
  *   Technoogies, Inc.</a>
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
-
-public class MemoryXAResource implements XAResource
-{
-  /** Logger.  */
-  private static final Logger logger =
-    Logger.getLogger(MemoryXAResource.class.getName());
-
-  /**
-   * Map from keyed from the {@link Integer} value of the various flags
-   * defined in {@link XAResource} and mapping to the formatted name for that
-   * flag.
-   */
-  private final static Map flagMap = new HashMap();
-
-  static {
-    flagMap.put(new Integer(XAResource.TMENDRSCAN),   "TMENDRSCAN");
-    flagMap.put(new Integer(XAResource.TMFAIL),       "TMFAIL");
-    flagMap.put(new Integer(XAResource.TMJOIN),       "TMJOIN");
-    flagMap.put(new Integer(XAResource.TMONEPHASE),   "TMONEPHASE");
-    flagMap.put(new Integer(XAResource.TMRESUME),     "TMRESUME");
-    flagMap.put(new Integer(XAResource.TMSTARTRSCAN), "TMSTARTRSCAN");
-    flagMap.put(new Integer(XAResource.TMSUCCESS),    "TMSUCCESS");
-    flagMap.put(new Integer(XAResource.TMSUCCESS),    "TMSUSPEND");
-  }
-
-  /** The transaction timeout value in seconds.  */
-  private int transactionTimeout = 0;
-
-  private XAResolverSession session;
-  private boolean rollback;
-  private Xid xid;
-
+public class MemoryXAResource
+    extends AbstractXAResource<RMInfo<MemoryXAResource.MemoryTxInfo>,MemoryXAResource.MemoryTxInfo> {
   //
   // Constructor
   //
@@ -94,209 +57,56 @@
    * Construct a {@link MemoryXAResource} with a specified transaction timeout.
    *
    * @param transactionTimeout  transaction timeout period, in seconds
+   * @param session             the underlying resolver-session to use
+   * @param resolverFactory     the resolver-factory we belong to
    */
   public MemoryXAResource(int transactionTimeout,
-                          XAResolverSession session)
-  {
-    logger.debug("<init> Creating MemoryXAResource: " + this);
-    this.transactionTimeout = transactionTimeout * 100;
-    this.session = session;
-    this.rollback = false;
+                          XAResolverSession session,
+                          ResolverFactory resolverFactory) {
+    super(transactionTimeout, resolverFactory, newTxInfo(session));
   }
 
-  //
-  // Methods implementing XAResource
-  //
-
-  public void start(Xid xid, int flags) throws XAException
-  {
-    logger.debug("Start " + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
-    switch (flags) {
-      case XAResource.TMRESUME:
-        logger.debug("Resuming transaction on " + System.identityHashCode(xid));
-        break;
-      case XAResource.TMNOFLAGS:
-        try {
-          session.refresh(new SimpleXAResource[] {});
-          this.xid = xid;
-        } catch (SimpleXAResourceException es) {
-          logger.warn("Failed to refresh phases", es);
-          throw new XAException(XAException.XAER_RMFAIL);
-        }
-        break;
-      default:
-        rollback = true;
-        logger.error("Unrecognised flags in start: " + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
-        throw new XAException(XAException.XAER_INVAL);
-    }
+  protected RMInfo<MemoryTxInfo> newResourceManager() {
+    return new RMInfo<MemoryTxInfo>();
   }
 
-  public int prepare(Xid xid) throws XAException
-  {
-    logger.debug("Prepare " + System.identityHashCode(xid));
-    logger.debug("Prepare always returns XA_OK, never XA_RDONLY");
-
-    if (rollback) {
-      logger.error("Attempting to prepare in failed transaction");
-      throw new XAException(XAException.XA_RBROLLBACK);
-    }
-    if (!xid.equals(this.xid)) {
-      logger.error("Attempting to prepare unknown transaction.");
-      throw new XAException(XAException.XAER_NOTA);
-    }
-
-    try {
-      session.prepare();
-    } catch (SimpleXAResourceException es) {
-      logger.warn("Attempt to prepare store failed", es);
-      throw new XAException(XAException.XA_RBROLLBACK);
-    }
-
-    return XA_OK;
+  private static MemoryTxInfo newTxInfo(XAResolverSession session) {
+    MemoryTxInfo ti = new MemoryTxInfo();
+    ti.session = session;
+    return ti;
   }
 
-  public void commit(Xid xid, boolean onePhase) throws XAException
-  {
-    logger.debug("Commit xid=" + System.identityHashCode(xid) + " onePhase=" + onePhase);
-    if (rollback) {
-      logger.error("Attempting to commit in failed transaction");
-      throw new XAException(XAException.XA_RBROLLBACK);
-    }
-    if (!xid.equals(this.xid)) {
-      logger.error("Attempting to commit unknown transaction.");
-      throw new XAException(XAException.XAER_NOTA);
-    }
+  //
+  // Methods implementing XAResource
+  //
 
-    try {
-      if (onePhase) {
-        // Check return value is XA_OK.
-        prepare(xid);
-      }
-    } catch (Throwable th) {
-      this.rollback = true;
-      logger.error("Attempt to prepare in onePhaseCommit failed.", th);
-      throw new XAException(XAException.XA_RBROLLBACK);
+  protected void doStart(MemoryTxInfo tx, int flags, boolean isNew) throws Exception {
+    if (flags == TMNOFLAGS || flags == TMJOIN) {
+      tx.session.refresh(new SimpleXAResource[] {});
     }
-
-    try {
-      session.commit();
-    } catch (Throwable th) {
-      // This is a serious problem since the database is now in an
-      // inconsistent state.
-      // Make sure the exception is logged.
-      logger.fatal("Failed to commit resource in transaction " + xid, th);
-      throw new XAException(XAException.XAER_RMERR);
-    }
   }
 
-  public void end(Xid xid, int flags) throws XAException
-  {
-    logger.debug("End xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+  protected void doEnd(MemoryTxInfo tx, int flags) {
   }
 
-  public void forget(Xid xid) throws XAException
-  {
-    logger.debug("Forget xid=" + System.identityHashCode(xid));
+  protected int doPrepare(MemoryTxInfo tx) throws Exception {
+    tx.session.prepare();
+    return XA_OK;
   }
 
-  public int getTransactionTimeout() throws XAException
-  {
-    logger.debug("Get transaction timeout: " + transactionTimeout);
-    return transactionTimeout;
+  protected void doCommit(MemoryTxInfo tx) throws Exception {
+    tx.session.commit();
   }
 
-  public boolean isSameRM(XAResource xaResource) throws XAException
-  {
-    logger.debug("Is same resource manager? " + (xaResource == this) + " :: " + xaResource + " on " + this);
-    return xaResource == this;
+  protected void doRollback(MemoryTxInfo tx) throws Exception {
+    tx.session.rollback();
   }
 
-  public Xid[] recover(int flag) throws XAException
-  {
-    logger.debug("Recover flag=" + formatFlags(flag));
-    throw new XAException(XAException.XAER_RMERR);
+  protected void doForget(MemoryTxInfo tx) {
   }
 
-  public void rollback(Xid xid) throws XAException
-  {
-    logger.debug("Rollback " + System.identityHashCode(xid));
-
-    boolean fatalError = false;
-
-    if (!xid.equals(this.xid)) {
-      logger.error("Attempting to rollback unknown transaction.");
-      fatalError = true;
-    }
-
-    try {
-      session.rollback();
-    } catch (Throwable th) {
-      // This is a serious problem since the database is now in an
-      // inconsistent state.
-      // Make sure the exception is logged.
-      logger.fatal("Failed to rollback resource in transaction " + xid, th);
-      fatalError = true;
-    }
-
-    if (fatalError) {
-      logger.fatal("Fatal error occured while rolling back transaction " + xid + " in manager for " + this.xid);
-      throw new XAException(XAException.XAER_RMERR);
-    }
+  static class MemoryTxInfo extends TxInfo {
+    /** the underlying resolver-session to use */
+    public XAResolverSession session;
   }
-
-  public boolean setTransactionTimeout(int transactionTimeout)
-    throws XAException
-  {
-    logger.debug("Set transaction timeout: " + transactionTimeout);
-    this.transactionTimeout = transactionTimeout;
-    return true;
-  }
-
-  //
-  // Internal methods
-  //
-
-  /**
-   * Format bitmasks defined by {@link XAResource}.
-   *
-   * @param flags  a bitmask composed from the constants defined in
-   *   {@link XAResource}
-   * @return a formatted representation of the <var>flags</var>
-   */
-  private static String formatFlags(int flags)
-  {
-    // Short-circuit evaluation if we've been explicitly passed no flags
-    if (flags == XAResource.TMNOFLAGS) {
-      return "TMNOFLAGS";
-    }
-
-    StringBuffer buffer = new StringBuffer();
-
-    // Add any flags that are present
-    for (Iterator i = flagMap.entrySet().iterator(); i.hasNext(); ) {
-      Map.Entry entry = (Map.Entry)i.next();
-      int entryFlag = ((Integer)entry.getKey()).intValue();
-
-      // If this flag is present, add it to the formatted output and remove
-      // from the bitmask
-      if ((entryFlag & flags) == entryFlag) {
-        if (buffer.length() > 0) {
-          buffer.append(",");
-        }
-        buffer.append(entry.getValue());
-        flags &= ~entryFlag;
-      }
-    }
-
-    // We would expect to have removed all flags by this point
-    // If there's some unknown flag we've missed, format it as hexadecimal
-    if (flags != 0) {
-      if (buffer.length() > 0) {
-        buffer.append(",");
-      }
-      buffer.append("0x").append(Integer.toHexString(flags));
-    }
-
-    return buffer.toString();
-  }
 }

Copied: trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/AbstractXAResource.java (from rev 690, branches/mgr-73/src/jar/resolver-spi/java/org/mulgara/resolver/spi/AbstractXAResource.java)
===================================================================
--- trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/AbstractXAResource.java	                        (rev 0)
+++ trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/AbstractXAResource.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2008 The Topaz Foundation 
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+
+package org.mulgara.resolver.spi;
+
+// Java 2 standard packages
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+/**
+ * A skeleton XAResource implementation. This handles the basic
+ * resource-manager and transaction management and ensures correct {@link
+ * #isSameRM} implementation. Subclasses must implement the actual
+ * functionality in the {@link #doStart}, {@link #doPrepare}, {@link
+ * #doCommit}, and {@link #doRollback} methods.
+ *
+ * @created 2008-02-16
+ * @author Ronald Tschalär
+ * @licence Apache License v2.0
+ */
+public abstract class AbstractXAResource<R extends AbstractXAResource.RMInfo<T>,T extends AbstractXAResource.TxInfo>
+    extends DummyXAResource {
+  /** Logger.  */
+  private static final Logger logger =
+    Logger.getLogger(AbstractXAResource.class.getName());
+
+  protected static final Map<ResolverFactory,RMInfo<? extends TxInfo>> resourceManagers =
+    new WeakHashMap<ResolverFactory,RMInfo<? extends TxInfo>>();
+
+  protected final R resourceManager;
+  protected final T tmpTxInfo;
+
+
+  //
+  // Constructor
+  //
+
+  /**
+   * Construct an XAResource.
+   *
+   * @param transactionTimeout  transaction timeout period, in seconds
+   * @param resolverFactory     the resolver-factory we belong to
+   * @param txInfo              the initial transaction-info
+   */
+  public AbstractXAResource(int transactionTimeout,
+                            ResolverFactory resolverFactory,
+                            T txInfo) {
+    super(transactionTimeout);
+
+    synchronized (resourceManagers) {
+      @SuppressWarnings("unchecked")
+      R rmgr = (R) resourceManagers.get(resolverFactory);
+      if (rmgr == null)
+        resourceManagers.put(resolverFactory, rmgr = newResourceManager());
+      this.resourceManager = rmgr;
+    }
+
+    this.tmpTxInfo = txInfo;
+  }
+
+  /**
+   * Create a new resource-manager instance - invoked only from the
+   * constructor and only when no resource-manager instance exists for the
+   * given resolver-factory.
+   */
+  protected abstract R newResourceManager();
+
+  //
+  // Methods implementing XAResource
+  //
+
+  public void start(Xid xid, int flags) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Start xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+      logger.debug("xid.format=" + xid.getFormatId() + " xid.gblTxId=" + Arrays.toString(xid.getGlobalTransactionId()) + " xid.brnchQual=" + Arrays.toString(xid.getBranchQualifier()));
+    }
+
+    T tx = resourceManager.transactions.get(new XidWrapper(xid));
+    boolean isNew = false;
+
+    switch (flags) {
+      case XAResource.TMRESUME:
+        if (tx == null) {
+          logger.error("Attempting to resume unknown transaction.");
+          throw new XAException(XAException.XAER_NOTA);
+        }
+        if (logger.isDebugEnabled()) {
+          logger.debug("Resuming transaction on xid=" + System.identityHashCode(xid));
+        }
+        break;
+
+      case XAResource.TMNOFLAGS:
+        if (tx != null) {
+          logger.warn("Received plain start for existing tx");
+          logger.warn("xid.format=" + xid.getFormatId() + " xid.gblTxId=" + Arrays.toString(xid.getGlobalTransactionId()) + " xid.brnchQual=" + Arrays.toString(xid.getBranchQualifier()));
+          throw new XAException(XAException.XAER_DUPID);
+        }
+        // fallthrough
+
+      case XAResource.TMJOIN:
+        if (tx == null) {
+          resourceManager.transactions.put(new XidWrapper(xid), tx = tmpTxInfo);
+          tx.xid = xid;
+          isNew = true;
+        }
+        break;
+
+      default:
+        // XXX: is this correct? Or should we actually roll back here?
+        if (tx != null)
+          tx.rollback = true;
+        logger.error("Unrecognised flags in start: xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+        throw new XAException(XAException.XAER_INVAL);
+    }
+
+    try {
+      doStart(tx, flags, isNew);
+    } catch (Throwable t) {
+      logger.warn("Failed to do start", t);
+      resourceManager.transactions.remove(new XidWrapper(xid));
+      throw new XAException(XAException.XAER_RMFAIL);
+    }
+  }
+
+  public void end(Xid xid, int flags) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("End xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+    }
+
+    T tx = resourceManager.transactions.get(new XidWrapper(xid));
+    if (tx == null) {
+      logger.error("Attempting to end unknown transaction.");
+      throw new XAException(XAException.XAER_NOTA);
+    }
+
+    try {
+      doEnd(tx, flags);
+    } catch (Throwable t) {
+      logger.warn("Failed to do end", t);
+      resourceManager.transactions.remove(new XidWrapper(xid));
+      throw new XAException(XAException.XAER_RMFAIL);
+    }
+  }
+
+  public int prepare(Xid xid) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Prepare xid=" + System.identityHashCode(xid));
+    }
+
+    T tx = resourceManager.transactions.get(new XidWrapper(xid));
+    if (tx == null) {
+      logger.error("Attempting to prepare unknown transaction.");
+      throw new XAException(XAException.XAER_NOTA);
+    }
+
+    if (tx.rollback) {
+      logger.info("Attempting to prepare in failed transaction");
+      rollback(xid);
+      throw new XAException(XAException.XA_RBROLLBACK);
+    }
+
+    try {
+      int sts = doPrepare(tx);
+      if (sts == XA_RDONLY)
+        resourceManager.transactions.remove(new XidWrapper(xid));
+      return sts;
+    } catch (Throwable t) {
+      logger.warn("Attempt to prepare failed", t);
+      rollback(xid);
+      throw new XAException(XAException.XA_RBROLLBACK);
+    }
+  }
+
+  public void commit(Xid xid, boolean onePhase) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Commit xid=" + System.identityHashCode(xid) + " onePhase=" + onePhase);
+    }
+
+    T tx = resourceManager.transactions.get(new XidWrapper(xid));
+    if (tx == null) {
+      logger.error("Attempting to commit unknown transaction.");
+      throw new XAException(XAException.XAER_NOTA);
+    }
+    if (tx.rollback) {
+      logger.error("Attempting to commit in failed transaction");
+      rollback(xid);
+      throw new XAException(XAException.XA_RBROLLBACK);
+    }
+
+    try {
+      if (onePhase) {
+        int sts = doPrepare(tx);
+        if (sts == XA_RDONLY) {
+          resourceManager.transactions.remove(new XidWrapper(xid));
+          return;
+        }
+      }
+    } catch (Throwable th) {
+      logger.error("Attempt to prepare in onePhaseCommit failed.", th);
+      rollback(xid);
+      throw new XAException(XAException.XA_RBROLLBACK);
+    }
+
+    boolean clean = true;
+    try {
+      doCommit(tx);
+    } catch (XAException xae) {
+      if (isHeuristic(xae)) {
+        clean = false;
+      }
+      throw xae;
+    } catch (Throwable th) {
+      // This is a serious problem since the database is now in an
+      // inconsistent state.
+      // Make sure the exception is logged.
+      logger.fatal("Failed to commit resource in transaction " + xid, th);
+      throw new XAException(XAException.XAER_RMERR);
+    } finally {
+      if (clean) {
+        resourceManager.transactions.remove(new XidWrapper(xid));
+      }
+    }
+  }
+
+  public void rollback(Xid xid) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Rollback xid=" + System.identityHashCode(xid));
+    }
+
+    T tx = resourceManager.transactions.get(new XidWrapper(xid));
+    if (tx == null) {
+      logger.error("Attempting to rollback unknown transaction.");
+      throw new XAException(XAException.XAER_NOTA);
+    }
+
+    boolean clean = true;
+    try {
+      doRollback(tx);
+    } catch (XAException xae) {
+      if (isHeuristic(xae)) {
+        clean = false;
+      }
+      throw xae;
+    } catch (Throwable th) {
+      // This is a serious problem since the database is now in an
+      // inconsistent state.
+      // Make sure the exception is logged.
+      logger.fatal("Failed to rollback resource in transaction " + xid, th);
+      throw new XAException(XAException.XAER_RMERR);
+    } finally {
+      if (clean) {
+        resourceManager.transactions.remove(new XidWrapper(xid));
+      }
+    }
+  }
+
+  public void forget(Xid xid) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Forget xid=" + System.identityHashCode(xid));
+    }
+
+    T tx = resourceManager.transactions.get(new XidWrapper(xid));
+    if (tx == null) {
+      logger.error("Attempting to forget unknown transaction.");
+      throw new XAException(XAException.XAER_NOTA);
+    }
+
+    boolean clean = true;
+    try {
+      doForget(tx);
+    } catch (XAException xae) {
+      if (xae.errorCode == XAException.XAER_RMERR) {
+        clean = false;
+      }
+      throw xae;
+    } catch (Throwable th) {
+      logger.error("Failed to forget transaction " + xid, th);
+      clean = false;
+      throw new XAException(XAException.XAER_RMERR);
+    } finally {
+      if (clean) {
+        resourceManager.transactions.remove(new XidWrapper(xid));
+      }
+    }
+  }
+
+  public Xid[] recover(int flag) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Recover flag=" + formatFlags(flag));
+    }
+
+    throw new XAException(XAException.XAER_RMERR);
+  }
+
+  public boolean isSameRM(XAResource xaResource) throws XAException {
+    boolean same = (xaResource instanceof AbstractXAResource) &&
+      ((AbstractXAResource)xaResource).resourceManager == resourceManager;
+
+    if (logger.isDebugEnabled()) {
+      logger.debug("Is same resource manager? " + same + " :: " + xaResource + " on " + this);
+    }
+
+    return same;
+  }
+
+  /** 
+   * Invoked on start with valid flags and tx state.
+   * 
+   * @param tx     the transaction being started; always non-null
+   * @param flags  one of TMNOFLAGS, TMRESUME, or TMJOIN
+   * @param isNew  true if <var>tx</var> was created as part of this start()
+   * @throws Exception 
+   */
+  protected abstract void doStart(T tx, int flags, boolean isNew) throws Exception;
+
+  /** 
+   * Invoked on end().
+   * 
+   * @param tx     the transaction being ended; always non-null
+   * @param flags  one of TMSUCCESS, TMFAIL, or TMSUSPEND
+   * @throws Exception 
+   */
+  protected abstract void doEnd(T tx, int flags) throws Exception;
+
+  /** 
+   * Invoked on prepare() or commit(onePhase=true).
+   * 
+   * @param tx  the transaction being prepared; always non-null
+   * @return XA_OK or XA_RDONLY
+   * @throws Exception 
+   */
+  protected abstract int doPrepare(T tx) throws Exception;
+
+  /** 
+   * Invoked on commit().
+   * 
+   * @param tx  the transaction being committed; always non-null
+   * @throws Exception 
+   */
+  protected abstract void doCommit(T tx) throws Exception;
+
+  /** 
+   * Invoked on (explicit or implicit) rollback().
+   * 
+   * @param tx  the transaction being rolled back; always non-null
+   * @throws Exception 
+   */
+  protected abstract void doRollback(T tx) throws Exception;
+
+  /** 
+   * Invoked on forget().
+   * 
+   * @param tx  the transaction to forget; always non-null
+   * @throws Exception 
+   */
+  protected abstract void doForget(T tx) throws Exception;
+
+  private static boolean isHeuristic(XAException xae) {
+    return (xae.errorCode == XAException.XA_HEURHAZ || xae.errorCode == XAException.XA_HEURCOM ||
+            xae.errorCode == XAException.XA_HEURRB  || xae.errorCode == XAException.XA_HEURMIX);
+  }
+
+
+  /** The resource-manager info */
+  public static class RMInfo<T extends TxInfo> {
+    /** the list of active transactions */
+    public final Map<XidWrapper,T> transactions =
+      Collections.synchronizedMap(new HashMap<XidWrapper,T>());
+  }
+
+  /** The info pertaining to a single transaction */
+  public static class TxInfo {
+    /** the underlying Xid of this transaction; not valid till the first start() */
+    public Xid xid;
+    /** true if this transaction has been marked for rollback */
+    public boolean rollback;
+  }
+
+  /**
+   * Xid-wrapper that implements hashCode() and equals(). JTA does not require
+   * Xid's to implement hashCode() and equals(), so in order to be able to use
+   * them as keys in a map we need to wrap them with something that implements
+   * them based on the individual fields of the Xid.
+   */
+  public static class XidWrapper {
+    private final Xid xid;
+    private final int hash;
+
+    public XidWrapper(Xid xid) {
+      this.xid = xid;
+      this.hash = Arrays.hashCode(xid.getBranchQualifier());
+    }
+
+    public int hashCode() {
+      return hash;
+    }
+
+    public boolean equals(Object other) {
+      Xid o;
+
+      if (other instanceof XidWrapper) {
+        o = ((XidWrapper)other).xid;
+      } else if (other instanceof Xid) {
+        o = (Xid)other;
+      } else {
+        return false;
+      }
+
+      if (o == xid)
+        return true;
+      return o.getFormatId() == xid.getFormatId() &&
+             Arrays.equals(o.getGlobalTransactionId(), xid.getGlobalTransactionId()) &&
+             Arrays.equals(o. getBranchQualifier(), xid. getBranchQualifier());
+    }
+  }
+}

Modified: trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/DummyXAResource.java
===================================================================
--- trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/DummyXAResource.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/DummyXAResource.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -54,15 +54,14 @@
 public class DummyXAResource implements XAResource
 {
   /** Logger.  */
-  private static final Logger logger =
-    Logger.getLogger(DummyXAResource.class.getName());
+  private static final Logger logger = Logger.getLogger(DummyXAResource.class.getName());
 
   /**
    * Map from keyed from the {@link Integer} value of the various flags
    * defined in {@link XAResource} and mapping to the formatted name for that
    * flag.
    */
-  private final static Map flagMap = new HashMap();
+  protected final static Map<Integer,String> flagMap = new HashMap<Integer,String>();
 
   static {
     flagMap.put(new Integer(XAResource.TMENDRSCAN),   "TMENDRSCAN");
@@ -76,20 +75,28 @@
   }
 
   /** The transaction timeout value in seconds.  */
-  private int transactionTimeout = 0;
+  protected int transactionTimeout = 0;
 
   //
   // Constructor
   //
 
   /**
+   * Construct a {@link DummyXAResource} with a default 10 second transaction timeout.
+   */
+  public DummyXAResource() {
+    this(10);
+  }
+
+  /**
    * Construct a {@link DummyXAResource} with a specified transaction timeout.
    *
    * @param transactionTimeout  transaction timeout period, in seconds
    */
-  public DummyXAResource(int transactionTimeout)
-  {
-    logger.debug("Creating DummyXAResource with timeout " + transactionTimeout);
+  public DummyXAResource(int transactionTimeout) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Creating " + getClass().getName() + " with timeout " + transactionTimeout);
+    }
     this.transactionTimeout = transactionTimeout;
   }
 
@@ -97,61 +104,70 @@
   // Methods implementing XAResource
   //
 
-  public void commit(Xid xid, boolean onePhase) throws XAException
-  {
-    logger.debug("Commit xid=" + System.identityHashCode(xid) + " onePhase=" + onePhase);
+  public void commit(Xid xid, boolean onePhase) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Commit xid=" + System.identityHashCode(xid) + " onePhase=" + onePhase);
+    }
   }
 
-  public void end(Xid xid, int flags) throws XAException
-  {
-    logger.debug("End xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+  public void end(Xid xid, int flags) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("End xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+    }
   }
 
-  public void forget(Xid xid) throws XAException
-  {
-    logger.debug("Forget xid=" + System.identityHashCode(xid));
+  public void forget(Xid xid) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Forget xid=" + System.identityHashCode(xid));
+    }
   }
 
-  public int getTransactionTimeout() throws XAException
-  {
-    logger.debug("Get transaction timeout: " + transactionTimeout);
+  public int getTransactionTimeout() throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Get transaction timeout: " + transactionTimeout);
+    }
     return transactionTimeout;
   }
 
-  public boolean isSameRM(XAResource xaResource) throws XAException
-  {
-    logger.debug("Is same resource manager? " + (xaResource == this));
+  public boolean isSameRM(XAResource xaResource) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Is same resource manager? " + (xaResource == this));
+    }
     return xaResource == this;
   }
 
-  public int prepare(Xid xid) throws XAException
-  {
-    logger.debug("Prepare " + System.identityHashCode(xid));
+  public int prepare(Xid xid) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Prepare " + System.identityHashCode(xid));
+    }
     return XA_OK;
   }
 
-  public Xid[] recover(int flag) throws XAException
-  {
-    logger.debug("Recover flag=" + formatFlags(flag));
+  public Xid[] recover(int flag) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Recover flag=" + formatFlags(flag));
+    }
     throw new XAException(XAException.XAER_RMERR);
   }
 
-  public void rollback(Xid xid) throws XAException
-  {
-    logger.debug("Rollback " + System.identityHashCode(xid));
+  public void rollback(Xid xid) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Rollback " + System.identityHashCode(xid));
+    }
   }
 
-  public boolean setTransactionTimeout(int transactionTimeout)
-    throws XAException
-  {
-    logger.debug("Set transaction timeout: " + transactionTimeout);
+  public boolean setTransactionTimeout(int transactionTimeout) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Set transaction timeout: " + transactionTimeout);
+    }
     this.transactionTimeout = transactionTimeout;
     return true;
   }
 
-  public void start(Xid xid, int flags) throws XAException
-  {
-    logger.debug("Start " + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+  public void start(Xid xid, int flags) throws XAException {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Start " + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+    }
   }
 
   //
@@ -161,23 +177,20 @@
   /**
    * Format bitmasks defined by {@link XAResource}.
    *
-   * @param flags  a bitmask composed from the constants defined in
-   *   {@link XAResource}
+   * @param flags  a bitmask composed from the constants defined in {@link XAResource}
    * @return a formatted representation of the <var>flags</var>
    */
-  private static String formatFlags(int flags)
-  {
+  protected static final String formatFlags(int flags) {
     // Short-circuit evaluation if we've been explicitly passed no flags
     if (flags == XAResource.TMNOFLAGS) {
       return "TMNOFLAGS";
     }
 
-    StringBuffer buffer = new StringBuffer();
+    StringBuilder buffer = new StringBuilder();
 
     // Add any flags that are present
-    for (Iterator i = flagMap.entrySet().iterator(); i.hasNext(); ) {
-      Map.Entry entry = (Map.Entry)i.next();
-      int entryFlag = ((Integer)entry.getKey()).intValue();
+    for (Map.Entry<Integer,String> entry : flagMap.entrySet()) {
+      int entryFlag = entry.getKey().intValue();
 
       // If this flag is present, add it to the formatted output and remove
       // from the bitmask

Modified: trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/TripleSetWrapperStatements.java
===================================================================
--- trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/TripleSetWrapperStatements.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/TripleSetWrapperStatements.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -71,8 +71,8 @@
   
   TObjectLongHashMap nodeMap;
 
-  Set<Triple> triples;
-  Iterator<Triple> iter;
+  Set<? extends Triple> triples;
+  Iterator<? extends Triple> iter;
   Triple currentTriple;
   boolean persistent;
 
@@ -80,7 +80,7 @@
   // Constructors
   //
 
-  public TripleSetWrapperStatements(Set<Triple> triples, ResolverSession resolverSession, int persistent)
+  public TripleSetWrapperStatements(Set<? extends Triple> triples, ResolverSession resolverSession, int persistent)
       throws TuplesException {
     this.triples = triples;
     this.resolverSession = resolverSession;

Modified: trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolver.java
===================================================================
--- trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolver.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolver.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,9 @@
  * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
  * Plugged In Software Pty Ltd. All Rights Reserved.
  *
- * Contributor(s): N/A.
+ * Contributor(s):
+ *    Move to associate XAResource with Resolver Factory
+ *     copyright 2008 The Topaz Foundation
  *
  * [NOTE: The text of this Exhibit A may differ slightly from the text
  * of the notices in the Source Code files of the Original Code. You
@@ -131,7 +133,8 @@
                          long systemModel,
                          URI modelTypeURI,
                          XAResolverSession resolverSession,
-                         XAStatementStore statementStore)
+                         XAStatementStore statementStore,
+                         ResolverFactory resolverFactory)
       throws IllegalArgumentException, ResolverFactoryException
   {
     // Validate parameters
@@ -153,7 +156,8 @@
     this.xaresource = new StatementStoreXAResource(
         10,  // transaction timeout in seconds
         resolverSession,
-        new SimpleXAResource[] { statementStore });
+        new SimpleXAResource[] { statementStore },
+        resolverFactory);
   }
 
   StatementStoreResolver(Resolver systemResolver,
@@ -161,7 +165,8 @@
                          long systemModel,
                          URI modelTypeURI,
                          XAResolverSession resolverSession,
-                         XAStatementStore statementStore)
+                         XAStatementStore statementStore,
+                         ResolverFactory resolverFactory)
       throws IllegalArgumentException, ResolverFactoryException
   {
     // Validate parameters
@@ -182,7 +187,8 @@
     this.xaresource = new StatementStoreXAResource(
         10,  // transaction timeout in seconds
         resolverSession,
-        new SimpleXAResource[] { statementStore });
+        new SimpleXAResource[] { statementStore },
+        resolverFactory);
   }
 
 
@@ -374,7 +380,7 @@
                                                + resolverSession.globalize(statements.getObject()) + " "
                                                + resolverSession.globalize(model) + "]", e);
       } catch (Exception eg) {
-	      throw new ResolverException("Failed to globalize in debug", eg);
+        throw new ResolverException("Failed to globalize in debug", eg);
       }
       throw new ResolverException("Couldn't make statement " + occurs + " in " + model, e);
     } catch (TuplesException e) {

Modified: trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolverFactory.java
===================================================================
--- trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolverFactory.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolverFactory.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -195,7 +195,8 @@
               : (XAResolverSession) resolverSessionFactory.newReadOnlyResolverSession(),
           allowWrites
               ? statementStore.newWritableStatementStore()
-              : statementStore.newReadOnlyStatementStore());
+              : statementStore.newReadOnlyStatementStore(),
+          this);
     } catch (ResolverSessionFactoryException er) {
       throw new ResolverFactoryException(
           "Failed to obtain a new ResolverSession", er);
@@ -216,7 +217,8 @@
           :
           (XAResolverSession) resolverSessionFactory.newReadOnlyResolverSession(),
           allowWrites ? statementStore.newWritableStatementStore()
-          : statementStore.newReadOnlyStatementStore());
+          : statementStore.newReadOnlyStatementStore(),
+          this);
     } catch (ResolverSessionFactoryException er) {
       throw new ResolverFactoryException(
           "Failed to obtain a new ResolverSession", er);

Modified: trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreXAResource.java
===================================================================
--- trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreXAResource.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreXAResource.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -17,6 +17,7 @@
  * Plugged In Software Pty Ltd. All Rights Reserved.
  *
  * Contributor(s): N/A.
+ *   Migration to AbstractXAResource copyright 2008 The Topaz Foundation
  *
  * [NOTE: The text of this Exhibit A may differ slightly from the text
  * of the notices in the Source Code files of the Original Code. You
@@ -28,22 +29,22 @@
 package org.mulgara.resolver.store;
 
 // Java 2 standard packages
-import java.util.*;
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
+import java.util.HashSet;
+import java.util.Set;
 
 // Third party packages
 import org.apache.log4j.Logger;
 
-
+import org.mulgara.resolver.spi.AbstractXAResource;
+import org.mulgara.resolver.spi.AbstractXAResource.RMInfo;
+import org.mulgara.resolver.spi.AbstractXAResource.TxInfo;
+import org.mulgara.resolver.spi.ResolverFactory;
 import org.mulgara.store.xa.SimpleXAResource;
 import org.mulgara.store.xa.SimpleXAResourceException;
 import org.mulgara.store.xa.XAResolverSession;
 
 /**
- * A dummy implementation of the {@link XAResource} interface which logs the
- * calls made to it, but otherwise ignores them.
+ * Implements the XAResource for the {@link StatementStoreResolver}.
  *
  * @created 2004-05-12
  * @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a>
@@ -55,40 +56,15 @@
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
 
-public class StatementStoreXAResource implements XAResource
-{
+public class StatementStoreXAResource
+    extends AbstractXAResource<RMInfo<StatementStoreXAResource.StatementStoreTxInfo>, StatementStoreXAResource.StatementStoreTxInfo> {
   /** Logger.  */
   private static final Logger logger =
     Logger.getLogger(StatementStoreXAResource.class.getName());
 
-  /**
-   * Map from keyed from the {@link Integer} value of the various flags
-   * defined in {@link XAResource} and mapping to the formatted name for that
-   * flag.
-   */
-  private final static Map flagMap = new HashMap();
-
-  static {
-    flagMap.put(new Integer(XAResource.TMENDRSCAN),   "TMENDRSCAN");
-    flagMap.put(new Integer(XAResource.TMFAIL),       "TMFAIL");
-    flagMap.put(new Integer(XAResource.TMJOIN),       "TMJOIN");
-    flagMap.put(new Integer(XAResource.TMONEPHASE),   "TMONEPHASE");
-    flagMap.put(new Integer(XAResource.TMRESUME),     "TMRESUME");
-    flagMap.put(new Integer(XAResource.TMSTARTRSCAN), "TMSTARTRSCAN");
-    flagMap.put(new Integer(XAResource.TMSUCCESS),    "TMSUCCESS");
-    flagMap.put(new Integer(XAResource.TMSUSPEND),    "TMSUSPEND");
-  }
-
-  /** The transaction timeout value in seconds.  */
-  private int transactionTimeout = 0;
-
-  private SimpleXAResource[] resources;
-  private XAResolverSession session;
-  private boolean rollback;
-  private Xid xid;
   // Used to prevent multiple calls to prepare on the store layer.
   // Set of session's that have been prepared.
-  private static Set preparing = new HashSet();
+  public static Set<XAResolverSession> preparing = new HashSet<XAResolverSession>();
 
   //
   // Constructor
@@ -98,292 +74,127 @@
    * Construct a {@link StatementStoreXAResource} with a specified transaction timeout.
    *
    * @param transactionTimeout  transaction timeout period, in seconds
+   * @param session             the underlying resolver-session to use
+   * @param resources           
+   * @param resolverFactory     the resolver-factory we belong to
    */
   public StatementStoreXAResource(int transactionTimeout,
                                   XAResolverSession session,
-                                  SimpleXAResource[] resources)
-  {
-    if (logger.isDebugEnabled()) {
-      logger.debug("<init> Creating StatementStoreXAResource: " + this);
-    }
-    this.transactionTimeout = transactionTimeout * 100;
-    this.resources = resources;
-    this.session = session;
+                                  SimpleXAResource[] resources,
+                                  ResolverFactory resolverFactory) {
+    super(transactionTimeout, resolverFactory, newTxInfo(session, resources));
   }
 
+  protected RMInfo<StatementStoreTxInfo> newResourceManager() {
+    return new RMInfo<StatementStoreTxInfo>();
+  }
+
+  private static StatementStoreTxInfo newTxInfo(XAResolverSession session,
+                                                SimpleXAResource[] resources) {
+    StatementStoreTxInfo ti = new StatementStoreTxInfo();
+    ti.session = session;
+    ti.resources = resources;
+    return ti;
+  }
+
   //
   // Methods implementing XAResource
   //
 
-  public void start(Xid xid, int flags) throws XAException
-  {
-    if (logger.isDebugEnabled()) {
-      logger.debug("Start " + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
-    } 
-    switch (flags) {
-      case XAResource.TMNOFLAGS:
-        try {
-          session.refresh(resources);
-          this.xid = xid;
-          this.rollback = false;
-        } catch (SimpleXAResourceException es) {
-          logger.error("Failed to obtain phases", es);
-          throw new XAException(XAException.XAER_RMFAIL);
-        }
-        break;
-      case XAResource.TMRESUME:
-        if (!xid.equals(this.xid)) {
-          logger.error("Attempt to resume resource in wrong transaction.");
-          throw new XAException(XAException.XAER_INVAL);
-        }
-        break;
-      case XAResource.TMJOIN:
-        if (!xid.equals(this.xid)) {
-          logger.error("Attempt to join with wrong transaction.");
-          throw new XAException(XAException.XAER_INVAL);
-        }
-        break;
-      default:  // Currently fall-through.
-        rollback = true;
-        logger.warn("Unrecognised flags in start: " + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
-        if (logger.isDebugEnabled()) {
-          logger.debug("This XAResource = " + System.identityHashCode(this.xid));
-        }
-        throw new XAException(XAException.XAER_INVAL);
+  protected void doStart(StatementStoreTxInfo tx, int flags, boolean isNew) throws Exception {
+    if (flags == TMNOFLAGS) {
+      tx.session.refresh(tx.resources);
     }
   }
 
-  public int prepare(Xid xid) throws XAException
-  {
-    if (logger.isDebugEnabled()) {
-      logger.debug("XAResource " + this + " Prepare " + System.identityHashCode(xid) + " With Session: " + System.identityHashCode(session));
-    }
+  protected void doEnd(StatementStoreTxInfo tx, int flags) {
+  }
 
-    if (rollback) {
-      logger.error("Attempting to prepare in failed transaction");
-      throw new XAException(XAException.XA_RBROLLBACK);
-    }
-    if (!xid.equals(this.xid)) {
-      logger.error("Attempting to prepare unknown transaction.");
-      throw new XAException(XAException.XAER_NOTA);
-    }
-    synchronized(preparing) {
-      if (preparing.contains(session)) {
+  protected int doPrepare(StatementStoreTxInfo tx) throws Exception {
+    synchronized (preparing) {
+      if (preparing.contains(tx.session)) {
         return XA_OK;
       } else {
-        preparing.add(session);
+        preparing.add(tx.session);
       }
     }
 
     try {
-      session.prepare();
+      tx.session.prepare();
     } catch (SimpleXAResourceException es) {
-      logger.warn("Attempt to prepare store failed", es);
-      synchronized(preparing) {
-        preparing.remove(session);
+      synchronized (preparing) {
+        preparing.remove(tx.session);
       }
-      throw new XAException(XAException.XA_RBROLLBACK);
+      throw es;
     }
 
     return XA_OK;
   }
 
-  public void commit(Xid xid, boolean onePhase) throws XAException
-  {
+  protected void doCommit(StatementStoreTxInfo tx) throws Exception {
     try {
-      if (logger.isDebugEnabled()) {
-        logger.debug("XAResource " + this + " Commit xid=" + System.identityHashCode(xid) + " onePhase=" + onePhase + " session=" + System.identityHashCode(session));
-      }
-      if (rollback) {
-        logger.error("Attempting to commit in failed transaction");
-        throw new XAException(XAException.XA_RBROLLBACK);
-      }
-      if (!xid.equals(this.xid)) {
-        logger.error("Attempting to commit unknown transaction.");
-        throw new XAException(XAException.XAER_NOTA);
-      }
-      try {
-        if (onePhase) {
-          // Currently prepare only returns XA_OK, and throws an exception on failure.
-          prepare(xid);
-        }
-      } catch (Throwable th) {
-        this.rollback = true;
-        logger.error("Attempt to prepare in onePhaseCommit failed.", th);
-        throw new XAException(XAException.XA_RBROLLBACK);
-      }
-
-      try {
-        session.commit();
-      } catch (Throwable th) {
-        // This is a serious problem since the database is now in an
-        // inconsistent state.
-        // Make sure the exception is logged.
-        logger.fatal("Failed to commit resource in transaction " + xid, th);
-        throw new XAException(XAException.XAER_RMERR);
-      }
+      tx.session.commit();
     } finally {
-      cleanup("commit");
+      cleanup("commit", tx);
     }
-
   }
 
-  public void end(Xid xid, int flags) throws XAException
-  {
-    if (logger.isDebugEnabled()) {
-      logger.debug("End xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
-    }
-  }
-
-  public void forget(Xid xid) throws XAException
-  {
-    if (logger.isDebugEnabled()) {
-      logger.debug("Forget xid=" + System.identityHashCode(xid));
-    }
+  protected void doForget(StatementStoreTxInfo tx) throws Exception {
     try {
-      synchronized(preparing) {
-        if (preparing.contains(session)) {
-          rollback(xid);
+      synchronized (preparing) {
+        if (preparing.contains(tx.session)) {
+          doRollback(tx);
         }
       }
     } finally {
-      cleanup("forget");
+      cleanup("forget", tx);
     }
   }
 
-  public int getTransactionTimeout() throws XAException
-  {
-    if (logger.isDebugEnabled()) {
-      logger.debug("Get transaction timeout: " + transactionTimeout);
-    }
-    return transactionTimeout;
-  }
-
-  public boolean isSameRM(XAResource xaResource) throws XAException
-  {
-    return xaResource == this;
-  }
-
-  public Xid[] recover(int flag) throws XAException
-  {
-    if (logger.isDebugEnabled()) {
-      logger.debug("Recover flag=" + formatFlags(flag));
-    }
-    throw new XAException(XAException.XAER_RMERR);
-  }
-
-  public void rollback(Xid xid) throws XAException
-  {
-    if (logger.isDebugEnabled()) {
-      logger.debug("Rollback " + System.identityHashCode(xid));
-    }
-
-    boolean fatalError = false;
-
-    if (!xid.equals(this.xid)) {
-      logger.error("Attempting to rollback unknown transaction.");
-      fatalError = true;
-    }
-
+  protected void doRollback(StatementStoreTxInfo tx) throws Exception {
     try {
-      if (logger.isDebugEnabled()) {
-        logger.debug("Rolling back phase");
-      }
-      session.rollback();
-    } catch (Throwable th) {
-      // This is a serious problem since the database is now in an
-      // inconsistent state.
-      // Make sure the exception is logged.
-      logger.fatal("Failed to rollback resource in transaction " + xid, th);
-      fatalError = true;
+      tx.session.rollback();
     } finally {
-      cleanup("rollback");
+      cleanup("rollback", tx);
     }
-
-    if (fatalError) {
-      logger.fatal("Fatal error occured while rolling back transaction " + xid + " in manager for " + this.xid);
-      throw new XAException(XAException.XAER_RMERR);
-    }
   }
 
-  public boolean setTransactionTimeout(int transactionTimeout)
-    throws XAException
-  {
-    if (logger.isDebugEnabled()) {
-      logger.debug("Set transaction timeout: " + transactionTimeout);
-    }
-    this.transactionTimeout = transactionTimeout;
-    return true;
-  }
-
   //
   // Internal methods
   //
 
-  /**
-   * Format bitmasks defined by {@link XAResource}.
-   *
-   * @param flags  a bitmask composed from the constants defined in
-   *   {@link XAResource}
-   * @return a formatted representation of the <var>flags</var>
-   */
-  private static String formatFlags(int flags)
-  {
-    // Short-circuit evaluation if we've been explicitly passed no flags
-    if (flags == XAResource.TMNOFLAGS) {
-      return "TMNOFLAGS";
+  private void cleanup(String operation, StatementStoreTxInfo tx) {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Performing cleanup from " + operation);
     }
-
-    StringBuffer buffer = new StringBuffer();
-
-    // Add any flags that are present
-    for (Iterator i = flagMap.entrySet().iterator(); i.hasNext(); ) {
-      Map.Entry entry = (Map.Entry)i.next();
-      int entryFlag = ((Integer)entry.getKey()).intValue();
-
-      // If this flag is present, add it to the formatted output and remove
-      // from the bitmask
-      if ((entryFlag & flags) == entryFlag) {
-        if (buffer.length() > 0) {
-          buffer.append(",");
-        }
-        buffer.append(entry.getValue());
-        flags &= ~entryFlag;
-      }
-    }
-
-    // We would expect to have removed all flags by this point
-    // If there's some unknown flag we've missed, format it as hexadecimal
-    if (flags != 0) {
-      if (buffer.length() > 0) {
-        buffer.append(",");
-      }
-      buffer.append("0x").append(Integer.toHexString(flags));
-    }
-
-    return buffer.toString();
-  }
-
-
-  private void cleanup(String operation) {
     try {
-      synchronized(preparing) {
-        if (preparing.contains(session)) {
-          preparing.remove(session);
+      synchronized (preparing) {
+        if (preparing.contains(tx.session)) {
+          preparing.remove(tx.session);
         } else {
-          logger.debug("Already committed/rolledback in this transaction");
+          if (logger.isDebugEnabled()) {
+            logger.debug("Already committed/rolledback in this transaction");
+          }
         }
       }
     } finally {
       try {
         if (logger.isDebugEnabled()) {
-          logger.debug("Releasing session after " + operation + " " + session);
+          logger.debug("Releasing session after " + operation + " " + tx.session);
         }
-        session.release();
-        session = null;
+        tx.session.release();
+        tx.session = null;
       } catch (SimpleXAResourceException es) {
         logger.error("Attempt to release store failed", es);
       }
     }
   }
+
+  static class StatementStoreTxInfo extends TxInfo {
+    /** the underlying resolver-session to use */
+    public XAResolverSession session;
+
+    /** the underlying resources */
+    public SimpleXAResource[] resources;
+  }
 }

Modified: trunk/src/jar/resolver-test/java/org/mulgara/resolver/test/TestResolverUnitTest.java
===================================================================
--- trunk/src/jar/resolver-test/java/org/mulgara/resolver/test/TestResolverUnitTest.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-test/java/org/mulgara/resolver/test/TestResolverUnitTest.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -131,7 +131,7 @@
     Session session = database.newSession();
 
     Variable[] varArray = new Variable[] { new Variable("a"), new Variable("b") };
-    List<Object> variables = Arrays.asList((Object[])varArray);
+    List<Variable> variables = Arrays.asList((Variable[])varArray);
 
     Answer answer = session.query(new Query(
       variables,                                                                // SELECT
@@ -178,7 +178,7 @@
     Session session = database.newSession();
 
     Variable[] varArray = new Variable[] { new Variable("a"), new Variable("c") };
-    List<Object> variables = Arrays.asList((Object[])varArray);
+    List<Variable> variables = Arrays.asList((Variable[])varArray);
 
     Answer answer = session.query(new Query(
       variables,                                                                // SELECT
@@ -223,7 +223,7 @@
     Session session = database.newSession();
 
     Variable[] varArray = new Variable[] { new Variable("a"), new Variable("b"), new Variable("c") };
-    List<Object> variables = Arrays.asList((Object[])varArray);
+    List<Variable> variables = Arrays.asList((Variable[])varArray);
 
     Answer answer = session.query(new Query(
       variables,                                                                // SELECT
@@ -274,7 +274,7 @@
     Session session = database.newSession();
 
     Variable[] varArray = new Variable[] { new Variable("a"), new Variable("b"), new Variable("c") };
-    List<Object> variables = Arrays.asList((Object[])varArray);
+    List<Variable> variables = Arrays.asList(varArray);
 
     Answer answer = session.query(new Query(
       variables,                                                                // SELECT
@@ -325,7 +325,7 @@
     Session session = database.newSession();
 
     Variable[] varArray = new Variable[] { new Variable("a"), new Variable("b"), new Variable("c") };
-    List<Object> variables = Arrays.asList((Object[])varArray);
+    List<Variable> variables = Arrays.asList(varArray);
 
 		try {
 			session.query(new Query(

Modified: trunk/src/jar/resolver-view/java/org/mulgara/resolver/view/ViewResolverUnitTest.java
===================================================================
--- trunk/src/jar/resolver-view/java/org/mulgara/resolver/view/ViewResolverUnitTest.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-view/java/org/mulgara/resolver/view/ViewResolverUnitTest.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -607,7 +607,7 @@
 
         // Evaluate the query
         Answer answer = new ArrayAnswer(session.query(new Query(
-          (List<Object>)(List)test.selectList,  // SELECT
+          test.selectList,          // SELECT
           test.model,               // FROM
           test.query,               // WHERE
           null,                     // HAVING

Modified: trunk/src/jar/server-beep/java/org/mulgara/server/beep/BEEPSession.java
===================================================================
--- trunk/src/jar/server-beep/java/org/mulgara/server/beep/BEEPSession.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/server-beep/java/org/mulgara/server/beep/BEEPSession.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
  * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
  * Plugged In Software Pty Ltd. All Rights Reserved.
  *
- * Contributor(s): N/A.
+ * Contributor(s):
+ *    XAResource addition copyright 2008 The Topaz Foundation
  *
  * [NOTE: The text of this Exhibit A may differ slightly from the text
  * of the notices in the Source Code files of the Original Code. You
@@ -32,6 +33,7 @@
 import java.net.*;
 import java.rmi.RemoteException;
 import java.util.*;
+import javax.transaction.xa.XAResource;
 
 // Third party packages
 import org.apache.log4j.Logger; // Apache Log4J
@@ -537,4 +539,11 @@
    throw new UnsupportedOperationException("This operation is only supported on local sessions.");
  }
 
+ public XAResource getXAResource() throws QueryException {
+   throw new QueryException("External transactions not implemented under Beep");
+ }
+
+ public XAResource getReadOnlyXAResource() throws QueryException {
+   throw new QueryException("External transactions not implemented under Beep");
+ }
 }

Modified: trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSession.java
===================================================================
--- trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSession.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSession.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
  * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
  * Plugged In Software Pty Ltd. All Rights Reserved.
  *
- * Contributor(s): N/A.
+ * Contributor(s):
+ *   XAResource access copyright 2007 The Topaz Foundation.
  *
  * [NOTE: The text of this Exhibit A may differ slightly from the text
  * of the notices in the Source Code files of the Original Code. You
@@ -308,4 +309,9 @@
   public void login(URI securityDomain, String username,
       char[] password) throws RemoteException;
 
+  /**
+   * Obtain an XAResource for this session.
+   */
+  public RemoteXAResource getXAResource() throws QueryException, RemoteException;
+  public RemoteXAResource getReadOnlyXAResource() throws QueryException, RemoteException;
 }

Modified: trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSessionWrapperSession.java
===================================================================
--- trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSessionWrapperSession.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSessionWrapperSession.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
  * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
  * Plugged In Software Pty Ltd. All Rights Reserved.
  *
- * Contributor(s): N/A.
+ * Contributor(s):
+ *   XAResource access copyright 2007 The Topaz Foundation.
  *
  * [NOTE: The text of this Exhibit A may differ slightly from the text
  * of the notices in the Source Code files of the Original Code. You
@@ -34,6 +35,7 @@
 import java.rmi.RemoteException;
 import java.util.*;
 import java.io.*;
+import javax.transaction.xa.XAResource;
 
 // Third party packages
 import org.apache.log4j.Logger;
@@ -210,7 +212,7 @@
   /**
    * {@inheritDoc}
    */
-  public void insert(URI modelURI, Set<Triple> statements) throws QueryException {
+  public void insert(URI modelURI, Set<? extends Triple> statements) throws QueryException {
 
     try {
       remoteSession.insert(modelURI, statements);
@@ -240,7 +242,7 @@
   /**
    * {@inheritDoc}
    */
-  public void delete(URI modelURI, Set<Triple> statements) throws QueryException {
+  public void delete(URI modelURI, Set<? extends Triple> statements) throws QueryException {
 
     try {
       remoteSession.delete(modelURI, statements);
@@ -610,4 +612,19 @@
     }
   }
 
+  public XAResource getXAResource() throws QueryException {
+    try {
+      return new RemoteXAResourceWrapperXAResource(remoteSession.getXAResource());
+    } catch (RemoteException re){
+      throw new QueryException("Java RMI failure", re);
+    }
+  }
+
+  public XAResource getReadOnlyXAResource() throws QueryException {
+    try {
+      return new RemoteXAResourceWrapperXAResource(remoteSession.getReadOnlyXAResource());
+    } catch (RemoteException re){
+      throw new QueryException("Java RMI failure", re);
+    }
+  }
 }

Copied: trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResource.java (from rev 690, branches/mgr-73/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResource.java)
===================================================================
--- trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResource.java	                        (rev 0)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResource.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2007 The Topaz Foundation 
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+package org.mulgara.server.rmi;
+
+// Java 2 standard packages
+import java.io.Serializable;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import org.apache.log4j.Category;
+
+// Local packages
+
+/**
+ * Remote XAResource.
+ *
+ */
+/**
+ * An analogue to XAResource that is suitable for implementing as an RMI object.
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ * @created 2007-11-28
+ * @licence Apache License v2.0
+ */
+public interface RemoteXAResource extends Remote
+{
+  public void commit(Xid xid, boolean onePhase) throws XAException, RemoteException;
+
+  public void end(Xid xid, int flags) throws XAException, RemoteException;
+
+  public void forget(Xid xid) throws XAException, RemoteException;
+
+  public int getTransactionTimeout() throws XAException, RemoteException;
+
+  public int prepare(Xid xid) throws XAException, RemoteException;
+
+  public Xid[] recover(int flag) throws XAException, RemoteException;
+
+  public void rollback(Xid xid) throws XAException, RemoteException;
+
+  public boolean setTransactionTimeout(int seconds) throws XAException, RemoteException;
+
+  public void start(Xid xid, int flags) throws XAException, RemoteException;
+
+  // Note: This provides distributed isSameRM
+  public Serializable getRMId() throws RemoteException;
+}

Copied: trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResourceWrapperXAResource.java (from rev 690, branches/mgr-73/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResourceWrapperXAResource.java)
===================================================================
--- trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResourceWrapperXAResource.java	                        (rev 0)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResourceWrapperXAResource.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2007 The Topaz Foundation 
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+package org.mulgara.server.rmi;
+
+// Java 2 standard packages
+import java.io.Serializable;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.Arrays;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import org.apache.log4j.*;
+
+// Local packages
+import org.mulgara.server.ResourceManagerInstanceAdaptor;
+
+/**
+ * Wraps an RMI RemoteXAResource and presents it as a pure XAResource.
+ *
+ * @created 2007-11-28
+ * @author Andrae Muys
+ * @licence Apache License v2.0
+ */
+class RemoteXAResourceWrapperXAResource implements XAResource, ResourceManagerInstanceAdaptor {
+  /** logger */
+  private static Logger logger =
+      Logger.getLogger(RemoteXAResourceWrapperXAResource.class.getName());
+
+  /**
+   * The wrapped instance.
+   */
+  private RemoteXAResource remoteResource;
+
+  /**
+   * Wrap a {@link RemoteAnswer} to make it into an {@link Answer}.
+   *
+   * @param remoteAnswer  the instance to wrap
+   * @throws IllegalArgumentException  if <var>remoteAnswer</var> is
+   *   <code>null</code>
+   */
+  RemoteXAResourceWrapperXAResource(RemoteXAResource remoteResource) throws RemoteException {
+    if (remoteResource == null) {
+      throw new IllegalArgumentException("Null 'remoteResource' parameter");
+    }
+
+    this.remoteResource = remoteResource;
+  }
+
+  //
+  // Methods implementing XAResource
+  //
+
+  public void commit(Xid xid, boolean onePhase) throws XAException {
+    try {
+      remoteResource.commit(convertXid(xid), onePhase);
+    } catch (RemoteException re) {
+      logger.warn("RMI Error in XAResource", re);
+      throw new XAException(XAException.XAER_RMFAIL);
+    }
+  }
+
+  public void end(Xid xid, int flags) throws XAException {
+    try {
+      remoteResource.end(convertXid(xid), flags);
+    } catch (RemoteException re) {
+      logger.warn("RMI Error in XAResource", re);
+      throw new XAException(XAException.XAER_RMFAIL);
+    }
+  }
+
+  public void forget(Xid xid) throws XAException {
+    try {
+      remoteResource.forget(convertXid(xid));
+    } catch (RemoteException re) {
+      logger.warn("RMI Error in XAResource", re);
+      throw new XAException(XAException.XAER_RMFAIL);
+    }
+  }
+
+  public int getTransactionTimeout() throws XAException {
+    try {
+      return remoteResource.getTransactionTimeout();
+    } catch (RemoteException re) {
+      logger.warn("RMI Error in XAResource", re);
+      throw new XAException(XAException.XAER_RMFAIL);
+    }
+  }
+
+  public boolean isSameRM(XAResource xares) throws XAException {
+    try {
+      if (xares == this) {
+        return true;
+      } else if (xares instanceof ResourceManagerInstanceAdaptor) {
+        try {
+          return ((ResourceManagerInstanceAdaptor)xares).getRMId().equals(remoteResource.getRMId());
+        } catch (UnsupportedOperationException eu) {
+          logger.debug("getRMId() unsupported on XAResource", eu);
+          return false;
+        }
+      } else {
+        return false;
+      }
+    } catch (RemoteException re) {
+      logger.warn("RMI Error in XAResource", re);
+      throw new XAException(XAException.XAER_RMFAIL);
+    }
+  }
+
+  public Serializable getRMId() {
+    try {
+      return remoteResource.getRMId();
+    } catch (RemoteException er) {
+      throw new UnsupportedOperationException("Failed to obtain RMid", er);
+    }
+  }
+
+  public int prepare(Xid xid) throws XAException {
+    try {
+      return remoteResource.prepare(convertXid(xid));
+    } catch (RemoteException re) {
+      logger.warn("RMI Error in XAResource", re);
+      throw new XAException(XAException.XAER_RMFAIL);
+    }
+  }
+
+  public Xid[] recover(int flag) throws XAException {
+    try {
+      return remoteResource.recover(flag);
+    } catch (RemoteException re) {
+      logger.warn("RMI Error in XAResource", re);
+      throw new XAException(XAException.XAER_RMFAIL);
+    }
+  }
+
+  public void rollback(Xid xid) throws XAException {
+    try {
+      remoteResource.rollback(convertXid(xid));
+    } catch (RemoteException re) {
+      logger.warn("RMI Error in XAResource", re);
+      throw new XAException(XAException.XAER_RMFAIL);
+    }
+  }
+
+  public boolean setTransactionTimeout(int seconds) throws XAException {
+    try {
+      return remoteResource.setTransactionTimeout(seconds);
+    } catch (RemoteException re) {
+      logger.warn("RMI Error in XAResource", re);
+      throw new XAException(XAException.XAER_RMFAIL);
+    }
+  }
+
+  public void start(Xid xid, int flags) throws XAException {
+    try {
+      remoteResource.start(convertXid(xid), flags);
+    } catch (RemoteException re) {
+      logger.warn("RMI Error in XAResource", re);
+      throw new XAException(XAException.XAER_RMFAIL);
+    }
+  }
+
+  private SerializableXid convertXid(Xid xid) {
+    return new SerializableXid(xid);
+  }
+
+  private static class SerializableXid implements Xid, Serializable {
+    private byte[] bq;
+    private int fi;
+    private byte[] gtid;
+
+    public SerializableXid(Xid xid) {
+      byte[] tbq = xid.getBranchQualifier();
+      byte[] tgtid = xid.getGlobalTransactionId();
+      this.bq = new byte[tbq.length];
+      this.fi = xid.getFormatId();
+      this.gtid = new byte[tgtid.length];
+      System.arraycopy(tbq, 0, this.bq, 0, tbq.length);
+      System.arraycopy(tgtid, 0, this.gtid, 0, tgtid.length);
+    }
+
+    public byte[] getBranchQualifier() {
+      return bq;
+    }
+
+    public int getFormatId() {
+      return fi;
+    }
+
+    public byte[] getGlobalTransactionId() {
+      return gtid;
+    }
+  }
+}

Modified: trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/SessionWrapperRemoteSession.java
===================================================================
--- trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/SessionWrapperRemoteSession.java	2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/SessionWrapperRemoteSession.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
  * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
  * Plugged In Software Pty Ltd. All Rights Reserved.
  *
- * Contributor(s): N/A.
+ * Contributor(s):
+ *   XAResource access copyright 2007 The Topaz Foundation.
  *
  * [NOTE: The text of this Exhibit A may differ slightly from the text
  * of the notices in the Source Code files of the Original Code. You
@@ -515,6 +516,24 @@
     session.login(securityDomain, username, password);
   }
 
+
+  public RemoteXAResource getXAResource() throws QueryException, RemoteException {
+    try {
+      return new XAResourceWrapperRemoteXAResource(session.getXAResource());
+    } catch (Throwable t) {
+      throw convertToQueryException(t);
+    }
+  }
+
+  public RemoteXAResource getReadOnlyXAResource() throws QueryException, RemoteException {
+    try {
+      return new XAResourceWrapperRemoteXAResource(session.getReadOnlyXAResource());
+    } catch (Throwable t) {
+      throw convertToQueryException(t);
+    }
+  }
+
+
   // Construct an exception chain that will pass over RMI.
   protected Throwable mapThrowable(Throwable t) {
     Throwable cause = t.getCause();

Copied: trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/XAResourceWrapperRemoteXAResource.java (from rev 690, branches/mgr-73/src/jar/server-rmi/java/org/mulgara/server/rmi/XAResourceWrapperRemoteXAResource.java)
===================================================================
--- trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/XAResourceWrapperRemoteXAResource.java	                        (rev 0)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/XAResourceWrapperRemoteXAResource.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2007 The Topaz Foundation 
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+package org.mulgara.server.rmi;
+
+// Java 2 standard packages
+import java.io.Serializable;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import org.apache.log4j.*;
+
+// Local packages
+import org.mulgara.server.ResourceManagerInstanceAdaptor;
+
+/**
+ * Wraps an XAResource and makes it available as an RMI object.
+ *
+ * @created 2007-11-28
+ * @author Andrae Muys
+ * @licence Apache License v2.0
+ */
+public class XAResourceWrapperRemoteXAResource
+    extends UnicastRemoteObject implements RemoteXAResource {
+  /** logger */
+  private static Logger logger =
+      Logger.getLogger(XAResourceWrapperRemoteXAResource.class.getName());
+
+  protected final XAResource resource;
+  protected final ResourceManagerInstanceAdaptor adaptor;
+
+  public XAResourceWrapperRemoteXAResource(XAResource resource) throws RemoteException {
+    if (resource == null) {
+      throw new IllegalArgumentException("Null 'resource' parameter");
+    }
+
+    this.resource = resource;
+    this.adaptor = resource instanceof ResourceManagerInstanceAdaptor ?
+      (ResourceManagerInstanceAdaptor)resource : null;
+  }
+
+  public void commit(Xid xid, boolean onePhase) throws XAException, RemoteException {
+    resource.commit(xid, onePhase);
+  }
+
+  public void end(Xid xid, int flags) throws XAException, RemoteException {
+    resource.end(xid, flags);
+  }
+
+  public void forget(Xid xid) throws XAException, RemoteException {
+    resource.forget(xid);
+  }
+
+  public int getTransactionTimeout() throws XAException, RemoteException {
+    return resource.getTransactionTimeout();
+  }
+
+  public boolean isSameRM(XAResource xares) throws XAException, RemoteException {
+    return resource.isSameRM(xares);
+  }
+
+  public int prepare(Xid xid) throws XAException, RemoteException {
+    return resource.prepare(xid);
+  }
+
+  public Xid[] recover(int flag) throws XAException, RemoteException {
+    return resource.recover(flag);
+  }
+
+  public void rollback(Xid xid) throws XAException, RemoteException {
+    resource.rollback(xid);
+  }
+
+  public boolean setTransactionTimeout(int seconds) throws XAException, RemoteException {
+    return resource.setTransactionTimeout(seconds);
+  }
+
+  public void start(Xid xid, int flags) throws XAException, RemoteException {
+    resource.start(xid, flags);
+  }
+
+  public Serializable getRMId() throws RemoteException, UnsupportedOperationException {
+    if (adaptor == null) {
+      throw new UnsupportedOperationException("Wrapped XAResource does not support remote-id");
+    } else {
+      return adaptor.getRMId();
+    }
+  }
+}

Copied: trunk/src/jar/util/java/org/mulgara/util/Assoc1toNMap.java (from rev 690, branches/mgr-73/src/jar/util/java/org/mulgara/util/Assoc1toNMap.java)
===================================================================
--- trunk/src/jar/util/java/org/mulgara/util/Assoc1toNMap.java	                        (rev 0)
+++ trunk/src/jar/util/java/org/mulgara/util/Assoc1toNMap.java	2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,102 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com) under contract to 
+ * Topaz Foundation. Portions created under this contract are
+ * Copyright (c) 2007 Topaz Foundation
+ * All Rights Reserved.
+ */
+
+package org.mulgara.util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A 1:N Associative Map.
+ *
+ * @created 2007-11-12
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ *
+ * @copyright &copy;2007 <a href="http://www.topazproject.org/">Topaz Project</a>
+ *
+ * @licence Open Software License v3.0</a>
+ */
+
+public class Assoc1toNMap<T1,T2> implements Iterable<Map.Entry<T1,Set<T2>>> {
+  private Map<T1, Set<T2>> map1toN;
+  private Map<T2, T1> mapNto1;
+
+  public Assoc1toNMap() {
+    map1toN = new HashMap<T1, Set<T2>>();
+    mapNto1 = new HashMap<T2, T1>();
+  }
+
+  public T1 get1(T2 t2) {
+    return mapNto1.get(t2);
+  }
+
+  public Set<T2> getN(T1 t1) {
+    return map1toN.get(t1);
+  }
+
+  public void put(T1 t1, T2 t2) {
+    Set<T2> t2set = getN(t1);
+    if (t2set == null) {
+      t2set = new HashSet<T2>();
+      map1toN.put(t1, t2set);
+    }
+    t2set.add(t2);
+    
+    mapNto1.put(t2, t1);
+  }
+
+  public void remove1(T1 t1) {
+    Set<T2> t2set = map1toN.remove(t1);
+    for (T2 t2 : t2set) {
+      mapNto1.remove(t2);
+    }
+  }
+
+  public void removeN(T2 t2) {
+    T1 t1 = mapNto1.remove(t2);
+    if (t1 != null) {
+      Set<T2> t2set = map1toN.get(t1);
+      t2set.remove(t2);
+      if (t2set.isEmpty()) {
+        map1toN.remove(t1);
+      }
+    }
+  }
+
+  public boolean contains1(T1 t1) {
+    return map1toN.containsKey(t1);
+  }
+
+  public boolean containsN(T2 t2) {
+    return mapNto1.containsKey(t2);
+  }
+
+  public Set<T1> getKeySet() {
+    return map1toN.keySet();
+  }
+
+  public Iterator<Map.Entry<T1,Set<T2>>> iterator() {
+    return map1toN.entrySet().iterator();
+  }
+}




More information about the Mulgara-svn mailing list