[Mulgara-svn] r1098 - in trunk: conf src/jar/query/java/org/mulgara/server src/jar/resolver/java/org/mulgara/resolver src/jar/resolver-relational/java/org/mulgara/resolver/relational 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

ronald at mulgara.org ronald at mulgara.org
Sun Jul 20 13:01:57 UTC 2008


Author: ronald
Date: 2008-07-20 06:01:56 -0700 (Sun, 20 Jul 2008)
New Revision: 1098

Added:
   trunk/src/jar/resolver/java/org/mulgara/resolver/MockResolver.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MockResolverFactory.java
Modified:
   trunk/conf/mulgara-config.xml
   trunk/conf/mulgara-embedded.dtd
   trunk/conf/mulgara-embedded.xsd
   trunk/src/jar/query/java/org/mulgara/server/Session.java
   trunk/src/jar/resolver-relational/java/org/mulgara/resolver/relational/RelationalResolverUnitTest.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/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/AppendAggregateTuples.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/BootstrapOperation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/CacheStatements.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/ContentFactory.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/ContentHandlerManagerImpl.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/CreateDefaultGraphOperation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/CreateModelOperation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/Database.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseFactory.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseFactoryInitializer.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseInitializer.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseOperationContext.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSecurityAdapterInitializer.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/DatabaseUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/DirectTransitiveFunction.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/ExhaustiveTransitiveFunction.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/GlobalizedAnswer.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolverFactory.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/JRDFResolverSession.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/JRDFResolverSessionFactory.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/LoginOperation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/ModelExistsOperation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.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/MulgaraTransaction.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MutableLocalQueryImpl.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/Operation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/PreallocateOperation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/QueryOperation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/RemoveModelOperation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/SetModelOperation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/StatusFormat.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/StreamContent.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/StringPoolSession.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/StringPoolSessionFactory.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/SystemModelSecurityAdapter.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/TransitiveFunction.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:
Merged branch mgr-121-lockrecovery.

This contains three sets of changes:
 1. support for idle and transaction timeouts - this closes #121, #122, and #125.
 2. synchronization fixes and simplifications for the transactions
 3. miscellaneous small fixes to the transactions - this closes #101.


Modified: trunk/conf/mulgara-config.xml
===================================================================
--- trunk/conf/mulgara-config.xml	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/conf/mulgara-config.xml	2008-07-20 13:01:56 UTC (rev 1098)
@@ -55,6 +55,9 @@
   <!-- Maximum duration in seconds for transactions, a positive integer -->
   <TransactionTimeout>604800</TransactionTimeout> <!-- one week -->
 
+  <!-- Maximum time in seconds a transaction may be idle, a positive integer -->
+  <IdleTimeout>3600</IdleTimeout> <!-- one hour -->
+
   <!--
     Database implementation to use, one of:
 

Modified: trunk/conf/mulgara-embedded.dtd
===================================================================
--- trunk/conf/mulgara-embedded.dtd	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/conf/mulgara-embedded.dtd	2008-07-20 13:01:56 UTC (rev 1098)
@@ -1,4 +1,4 @@
-<!ELEMENT MulgaraConfig (ExternalConfigPaths?, MulgaraHost?, Jetty?, ServerName, RMIPort?, PersistencePath, DefaultGraph?, TransactionTimeout? TripleStoreImplementation, StartupScript?)>
+<!ELEMENT MulgaraConfig (ExternalConfigPaths?, MulgaraHost?, Jetty?, ServerName, RMIPort?, PersistencePath, DefaultGraph?, TransactionTimeout?, IdleTimeout?. TripleStoreImplementation, StartupScript?)>
 
   <!ELEMENT ExternalConfigPaths (MulgaraLogging, WebDefault)>
 
@@ -27,6 +27,8 @@
 
   <!ELEMENT TransactionTimeout (#PCDATA)>
 
+  <!ELEMENT IdleTimeout (#PCDATA)>
+
   <!ELEMENT TripleStoreImplementation (#PCDATA)>
 
   <!ELEMENT StartupScript (#PCDATA)>

Modified: trunk/conf/mulgara-embedded.xsd
===================================================================
--- trunk/conf/mulgara-embedded.xsd	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/conf/mulgara-embedded.xsd	2008-07-20 13:01:56 UTC (rev 1098)
@@ -93,6 +93,7 @@
     </xs:complexType>
   </xs:element>
   <xs:element name="TransactionTimeout" type="xs:int"/>
+  <xs:element name="IdleTimeout" type="xs:int"/>
   <xs:element name="DefaultContentHandler">
     <xs:complexType>
       <xs:attribute name="type" type="xs:string" use="required"/>
@@ -119,7 +120,8 @@
         <xs:element ref="RMIPort" minOccurs="0"/>
         <xs:element ref="PersistencePath"/>
         <xs:element ref="DefaultGraph" minOccurs="0"/>
-        <xs:element ref="TransactionTimeout"/>
+        <xs:element ref="TransactionTimeout" minOccurs="0"/>
+        <xs:element ref="IdleTimeout" minOccurs="0"/>
         <xs:element ref="TripleStoreImplementation"/>
         <xs:element ref="RelatedQueryHandler"/>
         <xs:element ref="SecurityAdapterFactory" minOccurs="0" maxOccurs="unbounded"/>

Modified: trunk/src/jar/query/java/org/mulgara/server/Session.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/server/Session.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/query/java/org/mulgara/server/Session.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -346,6 +346,30 @@
    */
   public void login(URI securityDomain, String username, char[] password);
 
+  /** 
+   * The maximum time a transaction may be idle before it is aborted. If not set a default
+   * value is used. This value is only used for new transactions and does not affect any currently
+   * running transactions.
+   *
+   * <p>This currently only affects write transactions.
+   * 
+   * @param millis the number of milliseconds, or 0 for the default timeout
+   * @throws QueryException if there was an error talking to the server
+   */
+  public void setIdleTimeout(long millis) throws QueryException;
+
+  /** 
+   * The maximum time a transaction may be active (started but neither committed nor rolled back)
+   * before it is aborted. If not set a default value is used. This value is only used for new
+   * transactions and does not affect any currently running transactions.
+   *
+   * <p>This currently only affects write transactions.
+   * 
+   * @param millis the number of milliseconds, or 0 for the default timeout
+   * @throws QueryException if there was an error talking to the server
+   */
+  public void setTransactionTimeout(long millis) throws QueryException;
+
   /**
    * Obtain an XAResource for this Session.
    *

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -125,10 +125,15 @@
     suite.addTest(new AdvDatabaseSessionUnitTest("testImplicitCommitQuery"));
     suite.addTest(new AdvDatabaseSessionUnitTest("testConcurrentExplicitTxn"));
     suite.addTest(new AdvDatabaseSessionUnitTest("testConcurrentImplicitTxn"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testConcurrentImplicitRecovery"));
     suite.addTest(new AdvDatabaseSessionUnitTest("testPrefixingWithUnbound"));
     suite.addTest(new AdvDatabaseSessionUnitTest("testDatabaseDelete"));
     suite.addTest(new AdvDatabaseSessionUnitTest("testCreateModel"));
     suite.addTest(new AdvDatabaseSessionUnitTest("testInsertionBlankNodes"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testAutoCommitTransactionOps"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitTransactionFailure"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testAutoCommitTransactionFailure"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testSessionClose"));
 
     return suite;
   }
@@ -182,6 +187,7 @@
                    null,                            // no security domain
                    new JotmTransactionManagerFactory(),
                    0,                               // default transaction timeout
+                   0,                               // default idle timeout
                    nodePoolFactoryClassName,        // persistent
                    new File(persistenceDirectory, "xaNodePool"),
                    stringPoolFactoryClassName,      // persistent
@@ -199,6 +205,7 @@
 
       database.addResolverFactory("org.mulgara.resolver.url.URLResolverFactory", null);
       database.addResolverFactory("org.mulgara.resolver.xsd.XSDResolverFactory", null);
+      database.addResolverFactory("org.mulgara.resolver.MockResolverFactory", null);
     }
   }
 
@@ -242,47 +249,9 @@
       // Load some test data
       Session session = database.newSession();
       try {
-        Variable subjectVariable   = new Variable("subject");
-        Variable predicateVariable = new Variable("predicate");
-        Variable objectVariable    = new Variable("object");
-
-        List<SelectElement> selectList = new ArrayList<SelectElement>(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 answer = session.query(createQuery(modelURI));
+        compareResults(expectedResults(), answer);
         answer.close();
       } finally {
         session.close();
@@ -292,7 +261,7 @@
     }
   }
 
-  
+
   public void testConcurrentQuery() throws URISyntaxException {
     logger.info("Testing concurrentQuery");
 
@@ -300,45 +269,10 @@
       // Load some test data
       Session session = database.newSession();
       try {
-        Variable subjectVariable   = new Variable("subject");
-        Variable predicateVariable = new Variable("predicate");
-        Variable objectVariable    = new Variable("object");
-
-        List<SelectElement> selectList = new ArrayList<SelectElement>(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 answer1 = session.query(createQuery(modelURI));
 
-        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
-        ));
+        Answer answer2 = session.query(createQuery(modelURI));
 
         compareResults(answer1, answer2);
 
@@ -352,7 +286,7 @@
     }
   }
 
-  
+
   public void testSubqueryQuery() throws URISyntaxException {
     logger.info("Testing subqueryQuery");
 
@@ -427,7 +361,7 @@
     }
   }
 
-  
+
   public void testConcurrentSubqueryQuery() throws URISyntaxException {
     logger.info("Testing concurrentSubqueryQuery");
 
@@ -509,7 +443,7 @@
     }
   }
 
-  
+
   /**
    * Note: What this test does is a really bad idea - there is no
    *       isolation provided as each operation is within its own
@@ -524,32 +458,8 @@
       session.createModel(model2URI, null);
 
       try {
-        Variable subjectVariable   = new Variable("subject");
-        Variable predicateVariable = new Variable("predicate");
-        Variable objectVariable    = new Variable("object");
-
-        List<SelectElement> selectList = new ArrayList<SelectElement>(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
-        ));
+        Answer answer = session.query(createQuery(modelURI));
 
         answer.beforeFirst();
         while (answer.next()) {
@@ -560,37 +470,8 @@
         }
         answer.close();
 
-        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
-        ));
-        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);
+        Answer answer2 = session.query(createQuery(model2URI));
+        compareResults(expectedResults(), answer2);
         answer2.close();
 
         session.removeModel(model2URI);
@@ -602,7 +483,7 @@
     }
   }
 
-  
+
   public void testExplicitBasicQuery() throws URISyntaxException {
     logger.info("Testing basicQuery");
 
@@ -611,47 +492,10 @@
       Session session = database.newSession();
       try {
         session.setAutoCommit(false);
-        Variable subjectVariable   = new Variable("subject");
-        Variable predicateVariable = new Variable("predicate");
-        Variable objectVariable    = new Variable("object");
 
-        List<SelectElement> selectList = new ArrayList<SelectElement>(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 answer = session.query(createQuery(modelURI));
+        compareResults(expectedResults(), answer);
 
         session.setAutoCommit(true);
 
@@ -673,39 +517,15 @@
     }
   }
 
-  
+
   public void testAnswerWriteCloseIsolation() throws URISyntaxException {
     logger.info("Testing AnswerWriteCloseIsolation");
 
     try {
       Session session = database.newSession();
       try {
-        Variable subjectVariable   = new Variable("subject");
-        Variable predicateVariable = new Variable("predicate");
-        Variable objectVariable    = new Variable("object");
-
-        List<SelectElement> selectList = new ArrayList<SelectElement>(3);
-        selectList.add(subjectVariable);
-        selectList.add(predicateVariable);
-        selectList.add(objectVariable);
-
         // Evaluate the query
-        Answer answer = session.query(new Query(
-          selectList,                                       // SELECT
-          new ModelResource(systemModelURI),                      // 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 answer = session.query(createQuery(systemModelURI));
 
         session.setAutoCommit(false);
 
@@ -721,7 +541,7 @@
     }
   }
 
-  
+
   public void testExplicitIsolationQuery() throws URISyntaxException {
     logger.info("testExplicitIsolationQuery");
     URI fileURI  = new File("data/xatest-model1.rdf").toURI();
@@ -735,76 +555,17 @@
           session1.setAutoCommit(false);
           session1.setModel(model3URI, new ModelResource(fileURI));
 
-          Variable subjectVariable   = new Variable("subject");
-          Variable predicateVariable = new Variable("predicate");
-          Variable objectVariable    = new Variable("object");
-
-          List<SelectElement> selectList = new ArrayList<SelectElement>(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 answer = session2.query(createQuery(model3URI));
           answer.beforeFirst();
           assertFalse(answer.next());
           answer.close();
 
           session1.setAutoCommit(true);
 
-          selectList = new ArrayList<SelectElement>(3);
-          selectList.add(subjectVariable);
-          selectList.add(predicateVariable);
-          selectList.add(objectVariable);
-
           // Evaluate the query
-          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 = session2.query(createQuery(model3URI));
+          compareResults(expectedResults(), answer);
           answer.close();
 
           session1.removeModel(model3URI);
@@ -832,32 +593,8 @@
           session1.setAutoCommit(false);
           session1.setModel(model3URI, new ModelResource(fileURI));
 
-          Variable subjectVariable   = new Variable("subject");
-          Variable predicateVariable = new Variable("predicate");
-          Variable objectVariable    = new Variable("object");
-
-          List<SelectElement> selectList = new ArrayList<SelectElement>(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 answer = session2.query(createQuery(model3URI));
           answer.beforeFirst();
           assertFalse(answer.next());
           answer.close();
@@ -865,28 +602,8 @@
           session1.rollback();
           session1.setAutoCommit(true);
 
-          selectList = new ArrayList<SelectElement>(3);
-          selectList.add(subjectVariable);
-          selectList.add(predicateVariable);
-          selectList.add(objectVariable);
-
           // Evaluate the query
-          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 = session2.query(createQuery(model3URI));
 
           answer.beforeFirst();
           assertFalse(answer.next());
@@ -916,145 +633,33 @@
           session1.setAutoCommit(false);
           session1.setModel(model3URI, new ModelResource(fileURI));
 
-          Variable subjectVariable   = new Variable("subject");
-          Variable predicateVariable = new Variable("predicate");
-          Variable objectVariable    = new Variable("object");
-
-          List<SelectElement> selectList = new ArrayList<SelectElement>(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 answer = session2.query(createQuery(model3URI));
           answer.beforeFirst();
           assertFalse(answer.next());
           answer.close();
 
           session1.commit();
 
-          selectList = new ArrayList<SelectElement>(3);
-          selectList.add(subjectVariable);
-          selectList.add(predicateVariable);
-          selectList.add(objectVariable);
-
           // Evaluate the query
-          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 = session2.query(createQuery(model3URI));
 
-          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);
+          compareResults(expectedResults(), answer);
           answer.close();
 
           session1.removeModel(model3URI);
           session1.createModel(model3URI, null);
 
-          selectList = new ArrayList<SelectElement>(3);
-          selectList.add(subjectVariable);
-          selectList.add(predicateVariable);
-          selectList.add(objectVariable);
-
           // Evaluate the query
-          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 = session2.query(createQuery(model3URI));
 
-          results = new String[][] {
-            { "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);
+          compareResults(expectedResults(), answer);
           answer.close();
 
           session1.setAutoCommit(true);
 
-          selectList = new ArrayList<SelectElement>(3);
-          selectList.add(subjectVariable);
-          selectList.add(predicateVariable);
-          selectList.add(objectVariable);
-
           // Evaluate the query
-          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 = session2.query(createQuery(model3URI));
           answer.beforeFirst();
           assertFalse(answer.next());
           answer.close();
@@ -1082,79 +687,19 @@
           session1.createModel(model4URI, null);
           session1.setModel(model4URI, new ModelResource(fileURI));
 
-          Variable subjectVariable   = new Variable("subject");
-          Variable predicateVariable = new Variable("predicate");
-          Variable objectVariable    = new Variable("object");
-
-          List<SelectElement> selectList = new ArrayList<SelectElement>(3);
-          selectList.add(subjectVariable);
-          selectList.add(predicateVariable);
-          selectList.add(objectVariable);
-
           // Check data loaded
-          Answer answer = session1.query(new Query(
-            selectList,                                       // SELECT
-            new ModelResource(model4URI),                      // 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 answer = session1.query(createQuery(model4URI));
 
-          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);
+          compareResults(expectedResults(), answer);
           answer.close();
 
           // Delete all the data from the model
-          session2.delete(model4URI, new Query(
-            selectList,                                       // SELECT
-            new ModelResource(model4URI),                     // FROM
-            new ConstraintImpl(subjectVariable,               // WHERE
-                           predicateVariable,
-                           objectVariable),
-            null,                                             // HAVING
-            new ArrayList<Order>(),                           // ORDER BY
-            null,                                             // LIMIT
-            0,                                                // OFFSET
-            new UnconstrainedAnswer()                         // GIVEN
-          ));
+          session2.delete(model4URI, createQuery(model4URI));
 
           // Check all data removed from the model
           // This also checks that the delete successfully
           // performed the implicit commit.
-          answer = session1.query(new Query(
-            selectList,                                       // SELECT
-            new ModelResource(model4URI),                      // FROM
-            new ConstraintImpl(subjectVariable,               // WHERE
-                           predicateVariable,
-                           objectVariable),
-            null,                                             // HAVING
-            new ArrayList<Order>(),                           // ORDER BY
-            null,                                             // LIMIT
-            0,                                                // OFFSET
-            new UnconstrainedAnswer()                         // GIVEN
-          ));
+          answer = session1.query(createQuery(model4URI));
           answer.beforeFirst();
           assertFalse(answer.next());
           answer.close();
@@ -1179,61 +724,38 @@
   public void testInsertionBlankNodes()
   {
     logger.info("testInsertionBlankNodes");
-    
+
     try {
       Session session = database.newSession();
       try {
         session.createModel(model2URI, null);
         session.setAutoCommit(false);
-        
+
         URIReference refA = new URIReferenceImpl(URI.create("test:a"));
         URIReference refP1 = new URIReferenceImpl(URI.create("test:p1"));
         URIReference refP2 = new URIReferenceImpl(URI.create("test:p2"));
         Literal o1 = new LiteralImpl("o1");
         Literal o2 = new LiteralImpl("o2");
         BlankNode bn = new VariableNodeImpl("bn");
-        
+
         Set<Triple> insert1 = new HashSet<Triple>();
         insert1.add(new TripleImpl(refA, refP1, bn));
         insert1.add(new TripleImpl(bn, refP2, o1));
-        
+
         Set<Triple> insert2 = new HashSet<Triple>();
         insert2.add(new TripleImpl(refA, refP1, bn));
         insert2.add(new TripleImpl(bn, refP2, o2));
-        
+
         session.insert(model2URI, insert1);
         session.insert(model2URI, insert2);
         session.setAutoCommit(true);
-        
-        Variable subjectVariable   = new Variable("subject");
-        Variable predicateVariable = new Variable("predicate");
-        Variable objectVariable    = new Variable("object");
 
-        List<SelectElement> selectList = new ArrayList<SelectElement>(3);
-        selectList.add(subjectVariable);
-        selectList.add(predicateVariable);
-        selectList.add(objectVariable);
-        
-        Answer answer = session.query(new Query(
-              selectList,                                       // SELECT
-              new ModelResource(model2URI),                     // FROM
-              new ConstraintImpl(subjectVariable,               // WHERE
-                             predicateVariable,
-                             objectVariable),
-              null,                                             // HAVING
-              new ArrayList<Order>(),                           // ORDER BY
-              null,                                             // LIMIT
-              0,                                                // OFFSET
-              new UnconstrainedAnswer()                         // GIVEN
-            ));
-        
+        Answer answer = session.query(createQuery(model2URI));
         assertEquals(4, answer.getRowCount());
-      }
-      finally {
+      } finally {
         session.close();
       }
-    }
-    catch (Exception e) {
+    } catch (Exception e) {
       fail(e);
     }
   }
@@ -1267,48 +789,10 @@
                   tx2Started.notify();
                 }
 
-                Variable subjectVariable   = new Variable("subject");
-                Variable predicateVariable = new Variable("predicate");
-                Variable objectVariable    = new Variable("object");
-
-                List<SelectElement> selectList = new ArrayList<SelectElement>(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 answer = session2.query(createQuery(model3URI));
 
-                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);
+                compareResults(expectedResults(), answer);
                 answer.close();
 
                 session2.commit();
@@ -1389,32 +873,8 @@
             try {
               Session session2 = database.newSession();
               try {
-                Variable subjectVariable   = new Variable("subject");
-                Variable predicateVariable = new Variable("predicate");
-                Variable objectVariable    = new Variable("object");
-
-                List<SelectElement> selectList = new ArrayList<SelectElement>(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 answer = session2.query(createQuery(model3URI));
 
                 answer.beforeFirst();
                 assertFalse(answer.next());
@@ -1445,50 +905,77 @@
             try {
               Session session2 = database.newSession();
               try {
-                Variable subjectVariable   = new Variable("subject");
-                Variable predicateVariable = new Variable("predicate");
-                Variable objectVariable    = new Variable("object");
+                // Evaluate the query
+                Answer answer = session2.query(createQuery(model3URI));
+                compareResults(expectedResults(), answer);
+                answer.close();
 
-                List<SelectElement> selectList = new ArrayList<SelectElement>(3);
-                selectList.add(subjectVariable);
-                selectList.add(predicateVariable);
-                selectList.add(objectVariable);
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
 
+        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.setAutoCommit(true);
+        session1.removeModel(model3URI);
+
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  /**
+   * Test two simultaneous transactions, the first one obtains the write-lock
+   * and sleeps longer than the recovery timeout.
+   */
+  public void testConcurrentImplicitRecovery() throws URISyntaxException {
+    logger.info("testConcurrentImplicitRecovery");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    // test idle timeout
+    try {
+      Session session1 = database.newSession();
+      session1.setIdleTimeout(1000);
+      try {
+        session1.createModel(model3URI, null);
+        logger.debug("Obtaining autocommit for session1");
+        session1.setAutoCommit(false);
+        logger.debug("Obtained autocommit for session1");
+
+        Thread t2 = new Thread("tx2IdleTest") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              try {
+                logger.debug("Obtaining autocommit for session2");
+                session2.setAutoCommit(false);
+                logger.debug("Obtained autocommit for session2");
+
                 // 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 answer = session2.query(createQuery(model3URI));
 
-                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.beforeFirst();
+                assertFalse(answer.next());
                 answer.close();
 
+                logger.debug("Releasing autocommit for session2");
+                session2.setAutoCommit(true);
               } finally {
                 session2.close();
               }
@@ -1499,6 +986,10 @@
         };
         t2.start();
 
+        session1.setModel(model3URI, new ModelResource(fileURI));
+        logger.debug("Sleeping for 1sec");
+        Thread.sleep(1000);
+        logger.debug("Slept for 1sec");
         try {
           t2.join(2000L);
         } catch (InterruptedException ie) {
@@ -1507,18 +998,220 @@
         }
         assertFalse("second transaction should've terminated", t2.isAlive());
 
-        session1.setAutoCommit(true);
-        session1.removeModel(model3URI);
+        rollbackTimedOutTxn(session1);
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
 
+    // test transaction timeout
+    try {
+      Session session1 = database.newSession();
+      session1.setTransactionTimeout(1000);
+      try {
+        session1.createModel(model3URI, null);
+        logger.debug("Obtaining autocommit for session1");
+        session1.setAutoCommit(false);
+        logger.debug("Obtained autocommit for session1");
+
+        Thread t2 = new Thread("tx2TxToTest") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              try {
+                logger.debug("Obtaining autocommit for session2");
+                session2.setAutoCommit(false);
+                logger.debug("Obtained autocommit for session2");
+
+                // Evaluate the query
+                Answer answer = session2.query(createQuery(model3URI));
+
+                answer.beforeFirst();
+                assertFalse(answer.next());
+                answer.close();
+
+                logger.debug("Releasing autocommit for session2");
+                session2.setAutoCommit(true);
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+
+        session1.setModel(model3URI, new ModelResource(fileURI));
+        logger.debug("Sleeping for 1sec");
+        Thread.sleep(1000);
+        logger.debug("Slept for 1sec");
+        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());
+
+        rollbackTimedOutTxn(session1);
       } finally {
         session1.close();
       }
     } catch (Exception e) {
       fail(e);
     }
+
+    // test transaction timeout interrupting active operation
+    try {
+      Session session1 = database.newSession();
+      try {
+        session1.setTransactionTimeout(1000L);
+        session1.setAutoCommit(false);
+        logger.debug("Started transaction for session1");
+
+        URI delayTwoSecs = new URI("foo://mulgara/timeoutTest?active=mr&wait=2000");
+        session1.createModel(delayTwoSecs, new URI(Mulgara.NAMESPACE + "MockModel"));
+
+        try {
+          Answer answer = session1.query(createQuery(delayTwoSecs));
+          fail("query should've gotten interrupted");
+        } catch (QueryException qe) {
+          logger.debug("query was interrupted", qe);
+        }
+
+        rollbackTimedOutTxn(session1);
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+
+    // test transaction timeout timing out not-yet-active operation
+    try {
+      Session session1 = database.newSession();
+      try {
+        session1.setTransactionTimeout(1000L);
+        session1.setAutoCommit(false);
+        logger.debug("Started transaction for session1");
+
+        logger.debug("Sleeping for 2sec");
+        Thread.sleep(2000);
+        logger.debug("Slept for 2sec");
+
+        try {
+          Answer answer = session1.query(createQuery(model3URI));
+          fail("query should've gotten interrupted");
+        } catch (QueryException qe) {
+          logger.debug("query was interrupted", qe);
+        }
+
+        rollbackTimedOutTxn(session1);
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+
+    // test transaction timeout while operation is waiting for mutex
+    try {
+      final Session session1 = database.newSession();
+      try {
+        session1.setTransactionTimeout(1000L);
+        session1.setAutoCommit(false);
+        logger.debug("Started transaction for session1");
+
+        final URI delayTwoSecs = new URI("foo://mulgara/timeoutTest?active=mr&hardWait=2000");
+        session1.createModel(delayTwoSecs, new URI(Mulgara.NAMESPACE + "MockModel"));
+
+        Thread t2 = new Thread("timeoutTest") {
+          public void run() {
+            try {
+              try {
+                // this acquires mutex and holds it for 2s
+                Answer answer = session1.query(createQuery(delayTwoSecs));
+                Thread.sleep(100L);   // allow rollback to proceed
+                answer.beforeFirst();
+                assertFalse(answer.next());
+                answer.close();
+                fail("query should've gotten interrupted");
+              } catch (TuplesException te) {
+                logger.debug("query was interrupted", te);
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+        Thread.sleep(100L);
+
+        // blocks, waiting for mutex; when it does get it, it should rollback or see a rb
+        try {
+          Answer answer = session1.query(createQuery(model3URI));
+          Thread.sleep(100L);   // allow rollback to proceed
+          answer.beforeFirst();
+          assertFalse(answer.next());
+          answer.close();
+          fail("query should've gotten aborted");
+        } catch (QueryException qe) {
+          logger.debug("query was aborted", qe);
+        } catch (TuplesException te) {
+          logger.debug("query was aborted", te);
+        }
+
+        rollbackTimedOutTxn(session1);
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+
+    // test transaction timeout on autocommit transaction
+    try {
+      final Session session1 = database.newSession();
+      try {
+        session1.setTransactionTimeout(1000L);
+
+        final URI delayTwoSecs = new URI("foo://mulgara/timeoutTest?active=mr&hardWait=2000");
+        session1.createModel(delayTwoSecs, new URI(Mulgara.NAMESPACE + "MockModel"));
+
+        try {
+          // this acquires mutex and holds it for 2s
+          Answer answer = session1.query(createQuery(delayTwoSecs));
+          Thread.sleep(100L);
+          answer.beforeFirst();
+          assertFalse(answer.next());
+          answer.close();
+          fail("query should've gotten interrupted");
+        } catch (TuplesException te) {
+          logger.debug("query was interrupted", te);
+        }
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
   }
 
+  private void rollbackTimedOutTxn(Session session) throws QueryException {
+    try {
+      session.commit();
+      fail("Commit should have failed due to transaction timeout");
+    } catch (QueryException qe) {
+    }
 
+    logger.debug("Rolling back transaction on session");
+    session.rollback();
+    session.setAutoCommit(true);
+  }
+
   public void testPrefixingWithUnbound() throws URISyntaxException {
     logger.warn("testPrefixingWithUnbound");
     URI fileURI  = new File("data/prefix-unbound.rdf").toURI();
@@ -1603,10 +1296,408 @@
     database = null;
   }
 
+
+  /**
+   * Test various transaction operations in auto-commit mode.
+   */
+  public void testAutoCommitTransactionOps() {
+    logger.info("Testing autoCommitTransactionOps");
+
+    try {
+      Session session = database.newSession();
+      try {
+        // should be a no-op
+        session.setAutoCommit(true);
+
+        // commit should not be allowed
+        try {
+          session.commit();
+          fail("Commit did not fail in auto-commit mode");
+        } catch (QueryException qe) {
+        }
+
+        // rollback should not be allowed
+        try {
+          session.rollback();
+          fail("Rollback did not fail in auto-commit mode");
+        } catch (QueryException qe) {
+        }
+
+        // verify we're still good to go
+        session.query(createQuery(modelURI)).close();
+
+        // verify second setAutoCommit(false) is a no-op
+        session.createModel(model3URI, null);
+        session.setAutoCommit(false);
+        session.setModel(model3URI, new ModelResource(new File("data/xatest-model1.rdf").toURI()));
+        session.setAutoCommit(false);
+        session.commit();
+
+        Answer answer = session.query(createQuery(model3URI));
+        compareResults(expectedResults(), answer);
+        answer.close();
+
+        session.removeModel(model3URI);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  /**
+   * Test various operations after an explicit transaction fails.
+   */
+  public void testExplicitTransactionFailure() {
+    logger.info("Testing transactionExplicitFailure");
+
+    try {
+      // query after failure should fail
+      shouldFailExplicit(new TestOp() {
+        public void run(Session session) throws Exception {
+          session.query(createQuery(modelURI)).close();
+        }
+      }, "Query in failed transaction did not fail");
+
+      // insert after failure should fail
+      shouldFailExplicit(new TestOp() {
+        public void run(Session session) throws Exception {
+          session.insert(modelURI, Collections.singleton((Triple)new TripleImpl(
+                                                    new URIReferenceImpl(URI.create("test:a")),
+                                                    new URIReferenceImpl(URI.create("test:b")),
+                                                    new URIReferenceImpl(URI.create("test:c")))));
+        }
+      }, "Insert in failed transaction did not fail");
+
+      // commit after failure should fail
+      shouldFailExplicit(new TestOp() {
+        public void run(Session session) throws Exception {
+          session.commit();
+        }
+      }, "Commit in failed transaction did not fail");
+
+      // rollback after failure should succeed
+      shouldSucceedExplicit(new TestOp() {
+        public void run(Session session) throws Exception {
+          session.rollback();
+        }
+      });
+
+      // setAutoCommit(false) after failure should fail
+      shouldFailExplicit(new TestOp() {
+        public void run(Session session) throws Exception {
+          session.setAutoCommit(false);
+        }
+      }, "setAutoCommit(false) in failed transaction did not fail");
+
+      // setAutoCommit(true) after failure should succeed
+      shouldSucceedExplicit(new TestOp() {
+        public void run(Session session) throws Exception {
+          session.setAutoCommit(true);
+        }
+      });
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  private void shouldFailExplicit(TestOp op, String msg) throws Exception {
+    testExplicitTransactionFailureOp(op, true, msg);
+  }
+
+  private void shouldSucceedExplicit(TestOp op) throws Exception {
+    testExplicitTransactionFailureOp(op, false, null);
+  }
+
+  private void testExplicitTransactionFailureOp(TestOp op, boolean shouldFail, String msg) throws Exception {
+    Session session = database.newSession();
+    try {
+      // start tx
+      session.setAutoCommit(false);
+
+      // run bad query -> failed tx
+      try {
+        session.query(createQuery(URI.create("urn:no:such:model")));
+        fail("Bad query failed to throw exception");
+      } catch (QueryException qe) {
+      }
+
+      // run test op, verify it succeeds/fails, and reset
+      if (shouldFail) {
+        try {
+          op.run(session);
+          fail(msg);
+        } catch (QueryException qe) {
+        }
+        session.setAutoCommit(true);
+      } else {
+        op.run(session);
+      }
+
+      // verify we're good to go
+      session.query(createQuery(modelURI)).close();
+
+      session.setAutoCommit(false);
+      session.query(createQuery(modelURI)).close();
+      session.commit();
+    } finally {
+      session.close();
+    }
+  }
+
+  /**
+   * Test various operations after an operation in auto-commit fails.
+   */
+  public void testAutoCommitTransactionFailure() {
+    logger.info("Testing transactionAutoCommitFailure");
+
+    try {
+      // query after failure should succeed
+      shouldSucceedImplicit(new TestOp() {
+        public void run(Session session) throws Exception {
+          session.query(createQuery(modelURI)).close();
+        }
+      });
+
+      // insert after failure should succeed
+      shouldSucceedImplicit(new TestOp() {
+        public void run(Session session) throws Exception {
+          session.insert(modelURI, Collections.singleton((Triple)new TripleImpl(
+                                                    new URIReferenceImpl(URI.create("test:a")),
+                                                    new URIReferenceImpl(URI.create("test:b")),
+                                                    new URIReferenceImpl(URI.create("test:c")))));
+        }
+      });
+
+      // commit should fail
+      shouldFailImplicit(new TestOp() {
+        public void run(Session session) throws Exception {
+          session.commit();
+        }
+      }, "Commit after failed query did not fail");
+
+      // rollback should fail
+      shouldFailImplicit(new TestOp() {
+        public void run(Session session) throws Exception {
+          session.rollback();
+        }
+      }, "Rollback after failed query did not fail");
+
+      // setAutoCommit(false) after failure should succeed
+      shouldSucceedImplicit(new TestOp() {
+        public void run(Session session) throws Exception {
+          session.setAutoCommit(false);
+          session.setAutoCommit(true);
+        }
+      });
+
+      // setAutoCommit(true) after failure should succeed
+      shouldSucceedImplicit(new TestOp() {
+        public void run(Session session) throws Exception {
+          session.setAutoCommit(true);
+        }
+      });
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  private void shouldFailImplicit(TestOp op, String msg) throws Exception {
+    testImplicitTransactionFailureOp(op, true, msg);
+  }
+
+  private void shouldSucceedImplicit(TestOp op) throws Exception {
+    testImplicitTransactionFailureOp(op, false, null);
+  }
+
+  private void testImplicitTransactionFailureOp(TestOp op, boolean shouldFail, String msg) throws Exception {
+    Session session = database.newSession();
+    try {
+      // run bad query -> failed tx
+      try {
+        session.query(createQuery(URI.create("urn:no:such:model")));
+        fail("Bad query failed to throw exception");
+      } catch (QueryException qe) {
+      }
+
+      // run test op, verify it succeeds/fails
+      if (shouldFail) {
+        try {
+          op.run(session);
+          fail(msg);
+        } catch (QueryException qe) {
+        }
+      } else {
+        op.run(session);
+      }
+
+      // verify we're good to go
+      session.query(createQuery(modelURI)).close();
+
+      session.setAutoCommit(false);
+      session.query(createQuery(modelURI)).close();
+      session.setAutoCommit(true);
+    } finally {
+      session.close();
+    }
+  }
+
+  private static interface TestOp {
+    public void run(Session session) throws Exception;
+  }
+
+  /**
+   * Test session close on still-active session.
+   */
+  public void testSessionClose() {
+    logger.info("Testing sessionClose");
+
+    // test close while waiting for write-lock
+    try {
+      Session session1 = database.newSession();
+      session1.setAutoCommit(false);
+
+      try {
+        final Session session2 = database.newSession();
+
+        Thread t1 = new Thread("closeTest") {
+          public void run() {
+            try {
+              session2.setAutoCommit(false);
+              fail("Acquired write-lock unexpectedly");
+            } catch (QueryException qe) {
+              logger.debug("Caught expected exception", qe);
+            } finally {
+              try {
+                session2.close();
+              } catch (QueryException qe) {
+                logger.debug("Caught expected exception", qe);
+              }
+            }
+          }
+        };
+        t1.start();
+        Thread.sleep(100L);     // give thread some time to start and block
+
+        assertTrue("second session should still be active", t1.isAlive());
+
+        session2.close();
+
+        try {
+          t1.join(100L);
+        } catch (InterruptedException ie) {
+          logger.error("wait for thread-termination interrupted", ie);
+          fail(ie);
+        }
+        assertFalse("second session should've terminated", t1.isAlive());
+
+      } finally {
+        logger.debug("Releasing autocommit for session1");
+        session1.setAutoCommit(true);
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+
+    // test close while operation active in auto-commit
+    try {
+      final Session session1 = database.newSession();
+      final URI delayTwoSecs = new URI("foo://mulgara/closeTest?active=l&hardWait=1000");
+      session1.createModel(delayTwoSecs, new URI(Mulgara.NAMESPACE + "MockModel"));
+
+      final boolean[] closing = new boolean[1];
+
+      try {
+        Thread t1 = new Thread("closeTest") {
+          public void run() {
+            try {
+              Answer answer = session1.query(createQuery(delayTwoSecs));
+              answer.close();
+              synchronized (closing) {
+                assertTrue("close didn't block", closing[0]);
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t1.start();
+        Thread.sleep(100L);     // give thread some time to start and block
+
+        assertTrue("query should still be active", t1.isAlive());
+
+        synchronized (closing) { closing[0] = true; }
+        session1.close();
+        synchronized (closing) { closing[0] = false; }
+
+        try {
+          t1.join(100L);
+        } catch (InterruptedException ie) {
+          logger.error("wait for thread-termination interrupted", ie);
+          fail(ie);
+        }
+        assertFalse("second session should've terminated", t1.isAlive());
+
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
   //
   // Internal methods
   //
 
+  private Query createQuery(URI model) {
+    Variable subjectVariable   = new Variable("subject");
+    Variable predicateVariable = new Variable("predicate");
+    Variable objectVariable    = new Variable("object");
+
+    List<SelectElement> selectList = new ArrayList<SelectElement>(3);
+    selectList.add(subjectVariable);
+    selectList.add(predicateVariable);
+    selectList.add(objectVariable);
+
+    return new Query(
+      selectList,                                       // SELECT
+      new ModelResource(model),                         // 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
+    );
+  }
+
+  private String[][] expectedResults() {
+    return new String[][] {
+          { "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" },
+        };
+  }
+
   private void compareResults(String[][] expected, Answer answer) throws Exception {
     try {
       answer.beforeFirst();

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/AppendAggregateTuples.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/AppendAggregateTuples.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/AppendAggregateTuples.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -38,10 +38,8 @@
 import org.mulgara.query.rdf.LiteralImpl;
 import org.mulgara.resolver.spi.LocalizeException;
 import org.mulgara.resolver.spi.ResolverSession;
-import org.mulgara.store.statement.StatementStore;
 import org.mulgara.store.tuples.AbstractTuples;
 import org.mulgara.store.tuples.Tuples;
-import org.mulgara.store.tuples.TuplesOperations;
 import org.mulgara.util.StackTrace;
 
 /**

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -146,6 +146,7 @@
         null,                             // no security domain
         new JotmTransactionManagerFactory(),
         0,                                // default transaction timeout
+        0,                                // default idle timeout
         nodePoolFactoryClassName,         // persistent
         null,
         stringPoolFactoryClassName,       // persistent

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/BootstrapOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/BootstrapOperation.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/BootstrapOperation.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -10,7 +10,6 @@
 // Local packages
 import org.mulgara.query.rdf.URIReferenceImpl;
 import org.mulgara.resolver.spi.DatabaseMetadata;
-import org.mulgara.resolver.spi.ResolverSessionFactory;
 import org.mulgara.resolver.spi.SingletonStatements;
 import org.mulgara.resolver.spi.SystemResolver;
 

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/CacheStatements.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/CacheStatements.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/CacheStatements.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -28,7 +28,6 @@
 package org.mulgara.resolver;
 
 // Local packages
-import org.mulgara.query.Cursor;
 import org.mulgara.query.TuplesException;
 import org.mulgara.query.Variable;
 import org.mulgara.resolver.spi.Statements;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/ContentFactory.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/ContentFactory.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/ContentFactory.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -29,9 +29,6 @@
 
 // Java 2 standard packages
 import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.MalformedURLException;
-import java.io.File;
 import java.util.Map;
 import java.util.HashMap;
 import java.lang.reflect.Constructor;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/ContentHandlerManagerImpl.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/ContentHandlerManagerImpl.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/ContentHandlerManagerImpl.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -43,7 +43,6 @@
 import org.mulgara.content.ContentHandlerManager;
 import org.mulgara.content.ContentLoader;
 import org.mulgara.content.NotModifiedException;
-import org.mulgara.query.QueryException;
 import org.mulgara.resolver.spi.ResolverSession;
 import org.mulgara.resolver.spi.Statements;
 import org.mulgara.util.StackTrace;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/CreateDefaultGraphOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/CreateDefaultGraphOperation.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/CreateDefaultGraphOperation.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -23,7 +23,6 @@
 import org.mulgara.query.*;
 import org.mulgara.query.rdf.*;
 import org.mulgara.resolver.spi.*;
-import org.mulgara.server.Session;
 import org.mulgara.store.nodepool.NodePool;
 
 /**

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/CreateModelOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/CreateModelOperation.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/CreateModelOperation.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -39,7 +39,6 @@
 import org.mulgara.query.*;
 import org.mulgara.query.rdf.*;
 import org.mulgara.resolver.spi.*;
-import org.mulgara.server.Session;
 import org.mulgara.store.nodepool.NodePool;
 
 /**

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/Database.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/Database.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/Database.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -50,13 +50,10 @@
 
 import javax.naming.NamingException;
 import javax.transaction.SystemException;
-import javax.transaction.TransactionManager;
 
 import org.apache.log4j.Logger;
 import org.jrdf.vocabulary.RDF;
-import org.mulgara.content.Content;
 import org.mulgara.content.ContentHandler;
-import org.mulgara.query.LocalNode;
 import org.mulgara.query.QueryException;
 import org.mulgara.query.rdf.Mulgara;
 import org.mulgara.resolver.spi.DatabaseMetadata;
@@ -64,7 +61,6 @@
 import org.mulgara.resolver.spi.FactoryInitializer;
 import org.mulgara.resolver.spi.InitializerException;
 import org.mulgara.resolver.spi.LocalizeException;
-import org.mulgara.resolver.spi.Resolver;
 import org.mulgara.resolver.spi.ResolverException;
 import org.mulgara.resolver.spi.ResolverFactory;
 import org.mulgara.resolver.spi.ResolverFactoryException;
@@ -73,15 +69,11 @@
 import org.mulgara.resolver.spi.SecurityAdapterFactoryException;
 import org.mulgara.resolver.spi.SymbolicTransformation;
 import org.mulgara.resolver.spi.SystemResolverFactory;
-import org.mulgara.rules.RuleLoader;
 import org.mulgara.server.Session;
 import org.mulgara.server.SessionFactory;
 import org.mulgara.store.nodepool.NodePool;
 import org.mulgara.store.nodepool.NodePoolException;
-import org.mulgara.store.nodepool.NodePoolFactory;
-import org.mulgara.store.stringpool.StringPool;
 import org.mulgara.store.stringpool.StringPoolException;
-import org.mulgara.store.stringpool.StringPoolFactory;
 import org.mulgara.store.xa.SimpleXARecoveryHandler;
 import org.mulgara.store.xa.SimpleXAResourceException;
 import org.mulgara.transaction.TransactionManagerFactory;
@@ -246,10 +238,7 @@
   private final URI temporaryModelTypeURI;
 
   /**
-   * Factory for the {@link #transactionManager}.
-   *
-   * This only reason we hold a reference to this is so that it can be closed
-   * when the database shuts down.
+   * Factory for internal jta TransactionManager's.
    */
   private final TransactionManagerFactory transactionManagerFactory;
 
@@ -261,6 +250,11 @@
    */
   private final MulgaraTransactionManager transactionManager;
 
+  /** the default maximum duration for a transaction, in milli-seconds */
+  private final long defaultTransactionTimeout;
+  /** the default maximum idle time for a transaction, in milli-seconds */
+  private final long defaultIdleTimeout;
+
   /** The unique {@link URI} naming this database.  Not read in this implementation. */
   @SuppressWarnings("unused")
   private final URI uri;
@@ -323,6 +317,7 @@
          uri,        // security domain
          new JotmTransactionManagerFactory(),
          config.getTransactionTimeout(),
+         config.getIdleTimeout(),
          config.getPersistentNodePoolFactory().getType(),
          DatabaseFactory.subdir(
            directory,
@@ -376,9 +371,12 @@
    *   database is within, or <code>null</code> if this database is unsecured
    * @param transactionManagerFactory  the source for the
    *   {@link TransactionManager}, never <code>null</code>
-   * @param transactionTimeout  the number of seconds before transactions time
-   *   out, or zero to take the <var>transactionManagerFactory</var>'s default;
+   * @param transactionTimeout  the default number of seconds before transactions
+   *   time out, or zero to take the <var>transactionManagerFactory</var>'s default;
    *   never negative
+   * @param idleTimeout  the default number of seconds a transaction may be idle before
+   *   it is timed out, or zero to take the <var>transactionManagerFactory</var>'s
+   *   default; never negative
    * @param persistentNodePoolFactoryClassName  the name of a
    *   {@link NodePoolFactory} implementation which will be used to generate
    *   persistent local nodes, never <code>null</code>
@@ -416,6 +414,7 @@
                   URI    securityDomainURI,
                   TransactionManagerFactory transactionManagerFactory,
                   int    transactionTimeout,
+                  int    idleTimeout,
                   String persistentNodePoolFactoryClassName,
                   File   persistentNodePoolDirectory,
                   String persistentStringPoolFactoryClassName,
@@ -512,9 +511,10 @@
     assert this.contentHandlers != null;
 
     // FIXME: Migrate this code inside StringPoolSession.  Pass config to StringPoolSession.
-    this.transactionManager = new MulgaraTransactionManager(transactionManagerFactory);
+    this.transactionManager = new MulgaraTransactionManager();
 
-    transactionManager.setTransactionTimeout(transactionTimeout);
+    this.defaultTransactionTimeout = transactionTimeout * 1000L;
+    this.defaultIdleTimeout = idleTimeout * 1000L;
 
     // Enable resolver initialization
     if (logger.isDebugEnabled()) {
@@ -677,6 +677,7 @@
 
     DatabaseSession session = new DatabaseSession(
         transactionManager,
+        transactionManagerFactory,
         unmodifiableSecurityAdapterList,
         unmodifiableSymbolicTransformationList,
         spSessionFactory,
@@ -689,6 +690,8 @@
 		    contentHandlers,
         unmodifiableCachedResolverFactorySet,
         temporaryModelTypeURI,
+        defaultTransactionTimeout,
+        defaultIdleTimeout,
         ruleLoaderClassName);
 
     // Updates metadata to reflect bootstrapped system model.
@@ -888,6 +891,7 @@
     try {
       return new DatabaseSession(
         transactionManager,
+        transactionManagerFactory,
         unmodifiableSecurityAdapterList,
         unmodifiableSymbolicTransformationList,
         spSessionFactory,
@@ -900,6 +904,8 @@
         contentHandlers,
         unmodifiableCachedResolverFactorySet,
         temporaryModelTypeURI,
+        defaultTransactionTimeout,
+        defaultIdleTimeout,
         ruleLoaderClassName);
     } catch (ResolverFactoryException e) {
       throw new QueryException("Couldn't create session", e);
@@ -916,6 +922,7 @@
     try {
       return new LocalJRDFDatabaseSession(
           transactionManager,
+          transactionManagerFactory,
           unmodifiableSecurityAdapterList,
           unmodifiableSymbolicTransformationList,
           jrdfSessionFactory,
@@ -1140,6 +1147,7 @@
       try {
         return new DatabaseSession(
           transactionManager,
+          transactionManagerFactory,
           Collections.singletonList(
             (SecurityAdapter)new SystemModelSecurityAdapter(metadata.getSystemModelNode())
           ),
@@ -1154,6 +1162,8 @@
           contentHandlers,
           unmodifiableCachedResolverFactorySet,
           temporaryModelTypeURI,
+          defaultTransactionTimeout,
+          defaultIdleTimeout,
           ruleLoaderClassName);
       }
       catch (ResolverFactoryException e) {
@@ -1168,6 +1178,7 @@
       try {
         return new LocalJRDFDatabaseSession(
           transactionManager,
+          transactionManagerFactory,
           Collections.singletonList(
             new SystemModelSecurityAdapter(metadata.getSystemModelNode())
           ),

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseFactory.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseFactory.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseFactory.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -100,6 +100,7 @@
         uri,        // security domain
         new JotmTransactionManagerFactory(),
         config.getTransactionTimeout(),
+        config.getIdleTimeout(),
         config.getPersistentNodePoolFactory().getType(),
         subdir(directory, config.getPersistentNodePoolFactory().getDir()),
         config.getPersistentStringPoolFactory().getType(),

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseFactoryInitializer.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseFactoryInitializer.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseFactoryInitializer.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -37,8 +37,6 @@
 
 // Local packages
 import org.mulgara.resolver.spi.*;
-import org.mulgara.store.nodepool.NodePool;
-import org.mulgara.store.stringpool.StringPool;
 
 /**
  * The database initialiser used to provide configuration information to

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseInitializer.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseInitializer.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseInitializer.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -28,7 +28,6 @@
 package org.mulgara.resolver;
 
 // Third party packages
-import org.apache.log4j.Logger;  // Apache Log4J
 
 // Local packages
 import org.mulgara.resolver.spi.*;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseOperationContext.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseOperationContext.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseOperationContext.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -66,7 +66,6 @@
 import org.mulgara.resolver.spi.TuplesWrapperStatements;
 import org.mulgara.resolver.view.ViewMarker;
 import org.mulgara.resolver.view.SessionView;
-import org.mulgara.store.nodepool.NodePool;
 import org.mulgara.store.tuples.Tuples;
 import org.mulgara.store.tuples.TuplesOperations;
 

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSecurityAdapterInitializer.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSecurityAdapterInitializer.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSecurityAdapterInitializer.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -31,13 +31,11 @@
 import java.net.URI;
 
 // Third party packages
-import org.apache.log4j.Logger;  // Apache Log4J
 import org.jrdf.graph.Node;
 
 // Local packages
 import org.mulgara.query.QueryException;
 import org.mulgara.resolver.spi.*;
-import org.mulgara.server.Session;
 import org.mulgara.server.SessionFactory;
 
 /**

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -44,13 +44,12 @@
 import org.jrdf.graph.*;
 
 // Local packages
-import org.mulgara.content.ContentHandler;
 import org.mulgara.content.ContentHandlerManager;
 import org.mulgara.query.*;
 import org.mulgara.resolver.spi.*;
 import org.mulgara.rules.*;
 import org.mulgara.server.Session;
-import org.mulgara.store.nodepool.NodePool;
+import org.mulgara.transaction.TransactionManagerFactory;
 
 /**
  * A database session.
@@ -114,9 +113,21 @@
   private final MulgaraTransactionManager transactionManager;
 
   private MulgaraTransactionFactory transactionFactory;
-  private MulgaraInternalTransactionFactory internalFactory;
-  private MulgaraExternalTransactionFactory externalFactory;
+  private final MulgaraInternalTransactionFactory internalFactory;
+  private final MulgaraExternalTransactionFactory externalFactory;
 
+  /** the default maximum transaction duration */
+  private final long defaultTransactionTimeout;
+
+  /** the default maximum transaction idle time */
+  private final long defaultIdleTimeout;
+
+  /** the maximum transaction duration */
+  private long transactionTimeout;
+
+  /** the maximum transaction idle time */
+  private long idleTimeout;
+
   /** The name of the rule loader to use */
   private String ruleLoaderClassName;
 
@@ -134,6 +145,8 @@
    *
    * @param transactionManager  the source of transactions for this session,
    *   never <code>null</code>
+   * @param transactionManagerFactory  factory for internal jta transaction-manager
+   *   for this session, never <code>null</code>
    * @param securityAdapterList  {@link List} of {@link SecurityAdapter}s to be
    *   consulted before permitting operations, never <code>null</code>
    * @param symbolicTransformationList  {@link List} of
@@ -160,9 +173,17 @@
    *   never <code>null</code>
    * @param temporaryModelTypeURI  the URI of the model type to use to cache
    *   external models
+   * @param transactionTimeout  the default number of milli-seconds before transactions
+   *   time out, or zero to take the <var>transactionManagerFactory</var>'s default;
+   *   never negative
+   * @param idleTimeout  the default number of milli-seconds a transaction may be idle
+   *   before it is timed out, or zero to take the <var>transactionManagerFactory</var>'s
+   *   default; never negative
+   * @param ruleLoaderClassName  the rule-loader class to use; may be null 
    * @throws IllegalArgumentException if any argument is <code>null</code>
    */
   DatabaseSession(MulgaraTransactionManager transactionManager,
+      TransactionManagerFactory transactionManagerFactory,
       List<SecurityAdapter> securityAdapterList,
       List<SymbolicTransformation> symbolicTransformationList,
       ResolverSessionFactory resolverSessionFactory,
@@ -175,6 +196,8 @@
       ContentHandlerManager contentHandlers,
       Set<ResolverFactory> cachedResolverFactorySet,
       URI temporaryModelTypeURI,
+      long transactionTimeout,
+      long idleTimeout,
       String ruleLoaderClassName) throws ResolverFactoryException {
 
     if (logger.isDebugEnabled()) {
@@ -186,6 +209,8 @@
     // Validate parameters
     if (transactionManager == null) {
       throw new IllegalArgumentException("Null 'transactionManager' parameter");
+    } else if (transactionManagerFactory == null) {
+      throw new IllegalArgumentException("Null 'transactionManagerFactory' parameter");
     } else if (securityAdapterList == null) {
       throw new IllegalArgumentException("Null 'securityAdapterList' parameter");
     } else if (symbolicTransformationList == null) {
@@ -210,6 +235,10 @@
       throw new IllegalArgumentException("Null 'cachedResolverFactorySet' parameter");
     } else if (temporaryModelTypeURI == null) {
       throw new IllegalArgumentException("Null 'temporaryModelTypeURI' parameter");
+    } else if (transactionTimeout < 0) {
+      throw new IllegalArgumentException("negative 'transactionTimeout' parameter");
+    } else if (idleTimeout < 0) {
+      throw new IllegalArgumentException("negative 'idleTimeout' parameter");
     } else if (ruleLoaderClassName == null) {
       ruleLoaderClassName = DUMMY_RULE_LOADER;
     }
@@ -228,15 +257,19 @@
     this.contentHandlers            = contentHandlers;
     this.cachedResolverFactorySet   = cachedResolverFactorySet;
     this.temporaryModelTypeURI      = temporaryModelTypeURI;
+    this.defaultTransactionTimeout  = transactionTimeout;
+    this.defaultIdleTimeout         = idleTimeout;
     this.ruleLoaderClassName        = ruleLoaderClassName;
 
     this.transactionFactory = null;
-    this.internalFactory = null;
+    this.externalFactory = new MulgaraExternalTransactionFactory(this, transactionManager);
+    this.internalFactory =
+        new MulgaraInternalTransactionFactory(this, transactionManager, transactionManagerFactory);
 
+    this.transactionTimeout  = defaultTransactionTimeout;
+    this.idleTimeout         = defaultIdleTimeout;
+
     if (logger.isTraceEnabled()) logger.trace("Constructed DatabaseSession");
-
-    // Set the transaction timeout to an hour
-    transactionManager.setTransactionTimeout(3600);
   }
 
 
@@ -244,6 +277,7 @@
    * Non-rule version of the constructor.  Accepts all parameters except ruleLoaderClassName.
    */
   DatabaseSession(MulgaraTransactionManager transactionManager,
+      TransactionManagerFactory transactionManagerFactory,
       List<SecurityAdapter> securityAdapterList,
       List<SymbolicTransformation> symbolicTransformationList,
       ResolverSessionFactory resolverSessionFactory,
@@ -256,10 +290,10 @@
       ContentHandlerManager contentHandlers,
       Set<ResolverFactory> cachedResolverFactorySet,
       URI temporaryModelTypeURI) throws ResolverFactoryException {
-    this(transactionManager, securityAdapterList, symbolicTransformationList, resolverSessionFactory,
+    this(transactionManager, transactionManagerFactory, securityAdapterList, symbolicTransformationList, resolverSessionFactory,
         systemResolverFactory, temporaryResolverFactory, resolverFactoryList, externalResolverFactoryMap,
         internalResolverFactoryMap, metadata, contentHandlers, cachedResolverFactorySet,
-        temporaryModelTypeURI, "");
+        temporaryModelTypeURI, 0, 0, null);
   }
 
   //
@@ -556,7 +590,7 @@
     if (logger.isDebugEnabled()) logger.debug("setAutoCommit(" + autoCommit + ") called.");
     assertInternallyManagedXA();
     try {
-      internalFactory.setAutoCommit(this, autoCommit);
+      internalFactory.setAutoCommit(autoCommit);
     } catch (MulgaraTransactionException em) {
       throw new QueryException("Error setting autocommit", em);
     }
@@ -567,7 +601,7 @@
     logger.debug("Committing transaction");
     assertInternallyManagedXA();
     try {
-      internalFactory.commit(this);
+      internalFactory.commit();
     } catch (MulgaraTransactionException em) {
       throw new QueryException("Error performing commit", em);
     }
@@ -578,7 +612,7 @@
     logger.debug("Rollback transaction");
     assertInternallyManagedXA();
     try {
-      internalFactory.rollback(this);
+      internalFactory.rollback();
     } catch (MulgaraTransactionException em) {
       throw new QueryException("Error performing rollback", em);
     }
@@ -588,11 +622,18 @@
   public void close() throws QueryException {
     logger.debug("Closing session");
     try {
-      transactionManager.closingSession(this);
-      transactionFactory = null;
-    } catch (MulgaraTransactionException em2) {
-      logger.error("Error force-closing session", em2);
-      throw new QueryException("Error force-closing session.", em2);
+      if (transactionFactory != null)
+        transactionFactory.closingSession();
+    } catch (MulgaraTransactionException em) {
+      logger.error("Error force-closing session", em);
+      throw new QueryException("Error force-closing session.", em);
+    } finally {
+      try {
+        transactionManager.closingSession(this);
+      } catch (MulgaraTransactionException em2) {
+        logger.error("Error force-closing session", em2);
+        throw new QueryException("Error force-closing session.", em2);
+      }
     }
   }
 
@@ -678,7 +719,7 @@
   private void execute(Operation operation, String errorString) throws QueryException {
     ensureTransactionFactorySelected();
     try {
-      MulgaraTransaction transaction = transactionFactory.getTransaction(this, operation.isWriteOperation());
+      MulgaraTransaction transaction = transactionFactory.getTransaction(operation.isWriteOperation());
       transaction.execute(operation, metadata);
     } catch (MulgaraTransactionException em) {
       logger.debug("Error executing operation: " + errorString, em);
@@ -724,8 +765,8 @@
 
   private void assertInternallyManagedXA() throws QueryException {
     if (transactionFactory == null) {
-      transactionFactory = internalFactory = transactionManager.getInternalFactory();
-    } else if (internalFactory == null) {
+      transactionFactory = internalFactory;
+    } else if (transactionFactory != internalFactory) {
       throw new QueryException("Attempt to use internal transaction control in externally managed session");
     }
   }
@@ -733,8 +774,8 @@
 
   private void assertExternallyManagedXA() throws QueryException {
     if (transactionFactory == null) {
-      transactionFactory = externalFactory = transactionManager.getExternalFactory();
-    } else if (externalFactory == null) {
+      transactionFactory = externalFactory;
+    } else if (transactionFactory != externalFactory) {
       throw new QueryException("Attempt to use external transaction control in internally managed session");
     }
   }
@@ -742,15 +783,31 @@
 
   public XAResource getXAResource() throws QueryException {
     assertExternallyManagedXA();
-    return externalFactory.getXAResource(this, true);
+    return externalFactory.getXAResource(true);
   }
 
 
   public XAResource getReadOnlyXAResource() throws QueryException {
     assertExternallyManagedXA();
-    return externalFactory.getXAResource(this, false);
+    return externalFactory.getXAResource(false);
   }
-  
+
+  public void setIdleTimeout(long millis) {
+    idleTimeout = millis > 0 ? millis : defaultIdleTimeout;
+  }
+
+  public void setTransactionTimeout(long millis) {
+    transactionTimeout = millis > 0 ? millis : defaultTransactionTimeout;
+  }
+
+  public long getIdleTimeout() {
+    return idleTimeout;
+  }
+
+  public long getTransactionTimeout() {
+    return transactionTimeout;
+  }
+
   public boolean ping() {
     return true;
   }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionListQueryUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionListQueryUnitTest.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionListQueryUnitTest.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -156,6 +156,7 @@
         null, // no security domain
         new JotmTransactionManagerFactory(),
         0, // default transaction timeout
+        0, // default idle timeout
         nodePoolFactoryClassName, // persistent
         null,
         stringPoolFactoryClassName, // persistent

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionUnitTest.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionUnitTest.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -514,6 +514,7 @@
         null,                            // no security domain
         new JotmTransactionManagerFactory(),
         0,                               // default transaction timeout
+        0,                               // default idle timeout
         nodePoolFactoryClassName,        // persistent
         null,
         stringPoolFactoryClassName,      // persistent

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseUnitTest.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseUnitTest.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -41,9 +41,6 @@
 
 // Locally written packages
 import org.mulgara.query.*;
-import org.mulgara.store.StoreException;
-import org.mulgara.store.nodepool.NodePool;
-import org.mulgara.store.stringpool.StringPool;
 import org.mulgara.util.FileUtil;
 
 /**
@@ -111,7 +108,7 @@
   public void test1Constructor()
   {
     try {
-      new Database(null, null, null, null, 0, null, null, null, null, null,
+      new Database(null, null, null, null, 0, 0, null, null, null, null, null,
                    null, null, null, null, null, null, null, null, null);
       fail("Expected " + IllegalArgumentException.class);
     } catch (IllegalArgumentException e) {
@@ -158,6 +155,7 @@
           null,  // no security domain
           new JotmTransactionManagerFactory(),
           0,  // default transaction timeout
+          0,  // default idle timeout
           "org.mulgara.store.nodepool.memory.MemoryNodePoolFactory",
           null,
           "org.mulgara.store.stringpool.memory.MemoryStringPoolFactory",

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DirectTransitiveFunction.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DirectTransitiveFunction.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DirectTransitiveFunction.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -42,9 +42,6 @@
 import org.mulgara.resolver.spi.LocalizeException;
 import org.mulgara.resolver.spi.QueryEvaluationContext;
 import org.mulgara.resolver.spi.ResolverSession;
-import org.mulgara.store.statement.StatementStore;
-import org.mulgara.store.statement.StatementStoreException;
-import org.mulgara.store.stringpool.StringPool;
 import org.mulgara.store.tuples.LiteralTuples;
 import org.mulgara.store.tuples.Tuples;
 

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/ExhaustiveTransitiveFunction.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/ExhaustiveTransitiveFunction.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/ExhaustiveTransitiveFunction.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -38,9 +38,6 @@
 import org.mulgara.query.*;
 import org.mulgara.resolver.spi.QueryEvaluationContext;
 import org.mulgara.resolver.spi.ResolverSession;
-import org.mulgara.store.statement.StatementStore;
-import org.mulgara.store.statement.StatementStoreException;
-import org.mulgara.store.stringpool.StringPool;
 import org.mulgara.store.tuples.LiteralTuples;
 import org.mulgara.store.tuples.Tuples;
 

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -30,6 +30,7 @@
 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;
 
@@ -104,6 +105,7 @@
     suite.addTest(new ExternalTransactionUnitTest("testSimpleOnePhaseCommit"));
     suite.addTest(new ExternalTransactionUnitTest("testSimpleTwoPhaseCommit"));
     suite.addTest(new ExternalTransactionUnitTest("testBasicQuery"));
+    suite.addTest(new ExternalTransactionUnitTest("testBasicUpdate"));
     suite.addTest(new ExternalTransactionUnitTest("testMultipleQuery"));
     suite.addTest(new ExternalTransactionUnitTest("testBasicReadOnlyQuery"));
     suite.addTest(new ExternalTransactionUnitTest("testConcurrentQuery"));
@@ -120,6 +122,9 @@
     suite.addTest(new ExternalTransactionUnitTest("testInternalExternalConcurrentTxnRollback"));
     suite.addTest(new ExternalTransactionUnitTest("testExternalInternalConcurrentTxnRollback"));
     suite.addTest(new ExternalTransactionUnitTest("testInternalSerialMultipleSessions"));
+    suite.addTest(new ExternalTransactionUnitTest("testTransactionTimeout"));
+    suite.addTest(new ExternalTransactionUnitTest("testTransactionFailure"));
+    suite.addTest(new ExternalTransactionUnitTest("testSessionClose"));
 
     return suite;
   }
@@ -176,6 +181,7 @@
                    null,                            // no security domain
                    new JotmTransactionManagerFactory(),
                    0,                               // default transaction timeout
+                   0,                               // default idle timeout
                    nodePoolFactoryClassName,        // persistent
                    new File(persistenceDirectory, "xaNodePool"),
                    stringPoolFactoryClassName,      // persistent
@@ -192,6 +198,7 @@
                    "org.mulgara.content.rdfxml.RDFXMLContentHandler");
 
       database.addResolverFactory("org.mulgara.resolver.url.URLResolverFactory", null);
+      database.addResolverFactory("org.mulgara.resolver.MockResolverFactory", null);
     }
   }
 
@@ -297,49 +304,65 @@
         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");
+        // Evaluate the query
+        Answer answer = session.query(createQuery(modelURI));
+        compareResults(expectedResults(), answer);
+        answer.close();
 
-        List selectList = new ArrayList(3);
-        selectList.add(subjectVariable);
-        selectList.add(predicateVariable);
-        selectList.add(objectVariable);
+        resource.end(xid, XAResource.TMSUCCESS);
+        resource.commit(xid, true);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
 
-        // 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);
+  public void testBasicUpdate() throws URISyntaxException {
+    logger.info("Testing basicUpdate");
+
+    // straight insert and delete
+    try {
+      DatabaseSession session = (DatabaseSession)database.newSession();
+      try {
+        // start txn
+        XAResource resource = session.getXAResource();
+        Xid xid = new TestXid(1);
+        resource.start(xid, XAResource.TMNOFLAGS);
+
+        session.createModel(model2URI, null);
+
+        // insert data
+        session.insert(model2URI, Collections.singleton(new TripleImpl(
+            new URIReferenceImpl(URI.create("test:a")),
+            new URIReferenceImpl(URI.create("test:b")),
+            new URIReferenceImpl(URI.create("test:c")))));
+
+        // check it
+        Answer answer = session.query(createQuery(model2URI));
+        answer.beforeFirst();
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:a")), answer.getObject(0));
+        assertEquals(new URIReferenceImpl(new URI("test:b")), answer.getObject(1));
+        assertEquals(new URIReferenceImpl(new URI("test:c")), answer.getObject(2));
+        assertFalse(answer.next());
         answer.close();
 
+        // delete it
+        session.delete(model2URI, Collections.singleton(new TripleImpl(
+            new URIReferenceImpl(URI.create("test:a")),
+            new URIReferenceImpl(URI.create("test:b")),
+            new URIReferenceImpl(URI.create("test:c")))));
+
+        // check it
+        answer = session.query(createQuery(model2URI));
+        answer.beforeFirst();
+        assertFalse(answer.next());
+        answer.close();
+
+        session.removeModel(model2URI);
+
         resource.end(xid, XAResource.TMSUCCESS);
         resource.commit(xid, true);
       } finally {
@@ -348,6 +371,59 @@
     } catch (Exception e) {
       fail(e);
     }
+
+    // insert-select and delete-select
+    try {
+      DatabaseSession session = (DatabaseSession)database.newSession();
+      try {
+        // start txn
+        XAResource resource = session.getXAResource();
+        Xid xid = new TestXid(1);
+        resource.start(xid, XAResource.TMNOFLAGS);
+
+        session.createModel(model2URI, null);
+        session.createModel(model3URI, null);
+
+        // insert data
+        session.insert(model2URI, Collections.singleton(new TripleImpl(
+            new URIReferenceImpl(URI.create("test:a")),
+            new URIReferenceImpl(URI.create("test:b")),
+            new URIReferenceImpl(URI.create("test:c")))));
+
+        // insert-select
+        session.insert(model3URI, createQuery(model2URI));
+
+        // check it
+        Answer answer = session.query(createQuery(model3URI));
+        answer.beforeFirst();
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:a")), answer.getObject(0));
+        assertEquals(new URIReferenceImpl(new URI("test:b")), answer.getObject(1));
+        assertEquals(new URIReferenceImpl(new URI("test:c")), answer.getObject(2));
+        assertFalse(answer.next());
+        answer.close();
+
+        // delete it
+        session.delete(model3URI, createQuery(model2URI));
+
+        // check it
+        answer = session.query(createQuery(model3URI));
+        answer.beforeFirst();
+        assertFalse(answer.next());
+        answer.close();
+
+        // clean up
+        session.removeModel(model2URI);
+        session.removeModel(model3URI);
+
+        resource.end(xid, XAResource.TMSUCCESS);
+        resource.commit(xid, true);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
   }
 
   public void testMultipleQuery() throws URISyntaxException {
@@ -360,46 +436,10 @@
       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 answer1 = session.query(createQuery(modelURI));
+        Answer answer2 = session.query(createQuery(modelURI));
 
-        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();
@@ -426,47 +466,9 @@
         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 answer = session.query(createQuery(modelURI));
+        compareResults(expectedResults(), answer);
         answer.close();
 
         resource.end(xid, XAResource.TMSUCCESS);
@@ -490,47 +492,12 @@
       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
-        ));
+        Answer answer1 = session.query(createQuery(modelURI));
         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
-        ));
+        Answer answer2 = session.query(createQuery(modelURI));
         resource.end(xid2, XAResource.TMSUSPEND);
 
         compareResults(answer1, answer2);
@@ -570,35 +537,11 @@
       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
-        ));
+        Answer answer = session.query(createQuery(modelURI));
 
         roResource.end(xid2, XAResource.TMSUSPEND);
         answer.beforeFirst();
@@ -618,39 +561,10 @@
         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
-        ));
+        Answer answer2 = session.query(createQuery(model2URI));
 
         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);
+        compareResults(expectedResults(), answer2);
         answer2.close();
 
         Xid xid4 = new TestXid(4);
@@ -1068,7 +982,7 @@
           // Check committed phase is now updated and write-lock available
           rwResource.start(xid3, XAResource.TMNOFLAGS);
           assertChangeVisible(session2);
-          
+
           // Check internal transaction read-only
           assertChangeVisible(session1);
 
@@ -1094,78 +1008,16 @@
   }
 
   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
-    ));
+    Answer answer = session.query(createQuery(model3URI));
 
-    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);
+    compareResults(expectedResults(), 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 answer = session.query(createQuery(model3URI));
     answer.beforeFirst();
     assertFalse(answer.next());
     answer.close();
@@ -1207,48 +1059,10 @@
                   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 answer = session2.query(createQuery(model3URI));
 
-                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);
+                compareResults(expectedResults(), answer);
                 answer.close();
 
                 resource2.end(new TestXid(3), XAResource.TMSUCCESS);
@@ -1345,48 +1159,10 @@
                   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 answer = session2.query(createQuery(model3URI));
 
-                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);
+                compareResults(expectedResults(), answer);
                 answer.close();
 
                 session2.setAutoCommit(true);
@@ -1480,48 +1256,10 @@
                   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 answer = session2.query(createQuery(model3URI));
 
-                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);
+                compareResults(expectedResults(), answer);
                 answer.close();
 
                 resource.end(new TestXid(1), XAResource.TMSUCCESS);
@@ -1628,32 +1366,8 @@
                   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 answer = session2.query(createQuery(model3URI));
 
                 answer.beforeFirst();
                 assertFalse(answer.next());
@@ -1750,32 +1464,8 @@
                   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 answer = session2.query(createQuery(model3URI));
 
                 answer.beforeFirst();
                 assertFalse(answer.next());
@@ -1871,32 +1561,8 @@
 
         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 answer = session.query(createQuery(model3URI));
         answer.beforeFirst();
         assertFalse(answer.next());
         answer.close();
@@ -1908,28 +1574,9 @@
         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 = session.query(createQuery(model3URI));
 
         answer.beforeFirst();
         assertFalse(answer.next());
@@ -1950,8 +1597,7 @@
    * 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
-  {
+  public void testInternalSerialMultipleSessions() throws URISyntaxException {
     logger.info("testInternalSerialMultipleSessions");
     URI fileURI  = new File("data/xatest-model1.rdf").toURI();
 
@@ -1976,10 +1622,576 @@
     }
   }
 
+
+  /**
+   * Test transaction timeout.
+   */
+  public void testTransactionTimeout() throws URISyntaxException {
+    logger.info("testTransactionTimeout");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    // test idle timeout
+    try {
+      Session session1 = database.newSession();
+      session1.setIdleTimeout(1000);
+
+      try {
+        XAResource resource = session1.getXAResource();
+        Xid xid = new TestXid(1);
+        resource.start(xid, XAResource.TMNOFLAGS);
+
+        session1.createModel(model3URI, null);
+
+        resource.end(xid, XAResource.TMSUCCESS);
+        resource.commit(xid, true);
+
+        logger.debug("Starting transaction for session1");
+        resource.start(xid, XAResource.TMNOFLAGS);
+        logger.debug("Started transaction for session1");
+
+        Thread t2 = new Thread("tx2IdleTest") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              try {
+                logger.debug("Obtaining autocommit for session2");
+                session2.setAutoCommit(false);
+                logger.debug("Obtained autocommit for session2");
+
+                // Evaluate the query
+                Answer answer = session2.query(createQuery(model3URI));
+
+                answer.beforeFirst();
+                assertFalse(answer.next());
+                answer.close();
+
+                logger.debug("Releasing autocommit for session2");
+                session2.setAutoCommit(true);
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+
+        session1.setModel(model3URI, new ModelResource(fileURI));
+        logger.debug("Sleeping for 1sec");
+        Thread.sleep(1000);
+        logger.debug("Slept for 1sec");
+        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());
+
+        rollbackTimedOutTxn(resource, xid, true);
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+
+    // test transaction timeout
+    try {
+      Session session1 = database.newSession();
+      try {
+        XAResource resource = session1.getXAResource();
+        Xid xid = new TestXid(1);
+        resource.start(xid, XAResource.TMNOFLAGS);
+
+        session1.createModel(model3URI, null);
+
+        resource.end(xid, XAResource.TMSUCCESS);
+        resource.commit(xid, true);
+
+        logger.debug("Starting transaction for session1");
+        assertTrue(resource.setTransactionTimeout(1));
+        assertEquals(1, resource.getTransactionTimeout());
+        resource.start(xid, XAResource.TMNOFLAGS);
+        logger.debug("Started transaction for session1");
+
+        Thread t2 = new Thread("tx2Test") {
+          public void run() {
+            try {
+              Session session2 = database.newSession();
+              try {
+                logger.debug("Obtaining autocommit for session2");
+                session2.setAutoCommit(false);
+                logger.debug("Obtained autocommit for session2");
+
+                // Evaluate the query
+                Answer answer = session2.query(createQuery(model3URI));
+
+                answer.beforeFirst();
+                assertFalse(answer.next());
+                answer.close();
+
+                logger.debug("Releasing autocommit for session2");
+                session2.setAutoCommit(true);
+              } finally {
+                session2.close();
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+
+        session1.setModel(model3URI, new ModelResource(fileURI));
+        logger.debug("Sleeping for 1sec");
+        Thread.sleep(1000);
+        logger.debug("Slept for 1sec");
+        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());
+
+        rollbackTimedOutTxn(resource, xid, true);
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+
+    // test transaction timeout interrupting active operation
+    try {
+      Session session1 = database.newSession();
+      try {
+        XAResource resource = session1.getXAResource();
+
+        assertTrue(resource.setTransactionTimeout(1));
+        assertEquals(1, resource.getTransactionTimeout());
+
+        Xid xid = new TestXid(1);
+        resource.start(xid, XAResource.TMNOFLAGS);
+        logger.debug("Started transaction for session1");
+
+        URI delayTwoSecs = new URI("foo://mulgara/timeoutTest?active=mr&hardWait=2000");
+        session1.createModel(delayTwoSecs, new URI(Mulgara.NAMESPACE + "MockModel"));
+
+        try {
+          Answer answer = session1.query(createQuery(delayTwoSecs));
+          Thread.sleep(100L);
+          answer.beforeFirst();
+          assertFalse(answer.next());
+          answer.close();
+          fail("query should've gotten interrupted");
+        } catch (TuplesException te) {
+          logger.debug("query was interrupted", te);
+        }
+
+        rollbackTimedOutTxn(resource, xid, true);
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+
+    // test transaction timeout while operation is waiting for mutex
+    try {
+      final Session session1 = database.newSession();
+      try {
+        XAResource resource = session1.getXAResource();
+
+        assertTrue(resource.setTransactionTimeout(1));
+        assertEquals(1, resource.getTransactionTimeout());
+
+        Xid xid = new TestXid(1);
+        resource.start(xid, XAResource.TMNOFLAGS);
+        logger.debug("Started transaction for session1");
+
+        final URI delayTwoSecs = new URI("foo://mulgara/timeoutTest?active=mr&hardWait=2000");
+        session1.createModel(delayTwoSecs, new URI(Mulgara.NAMESPACE + "MockModel"));
+
+        Thread t2 = new Thread("timeoutTest") {
+          public void run() {
+            try {
+              try {
+                // this acquires mutex and holds it for 2s
+                Answer answer = session1.query(createQuery(delayTwoSecs));
+                Thread.sleep(100L);   // allow rollback to proceed
+                answer.beforeFirst();
+                assertFalse(answer.next());
+                answer.close();
+                fail("query should've gotten interrupted");
+              } catch (TuplesException te) {
+                logger.debug("query was interrupted", te);
+              }
+            } catch (Exception e) {
+              fail(e);
+            }
+          }
+        };
+        t2.start();
+        Thread.sleep(100L);
+
+        // blocks, waiting for mutex; when it does get it, it should rollback or see a rb
+        try {
+          Answer answer = session1.query(createQuery(model3URI));
+          Thread.sleep(100L);   // allow rollback to proceed
+          answer.beforeFirst();
+          assertFalse(answer.next());
+          answer.close();
+          fail("query should've gotten aborted");
+        } catch (TuplesException te) {
+          logger.debug("query was aborted", te);
+        }
+
+        t2.join(500);
+        assertFalse("timeout-test thread should've terminated", t2.isAlive());
+
+        rollbackTimedOutTxn(resource, xid, true);
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  private void rollbackTimedOutTxn(XAResource resource, Xid xid, boolean tryCommit) throws XAException {
+    try {
+      resource.end(xid, XAResource.TMSUCCESS);
+      if (tryCommit) {
+        resource.commit(xid, true);
+        fail("Commit should have failed due to transaction timeout");
+      }
+    } catch (XAException xae) {
+    }
+
+    logger.debug("Rolling back transaction");
+    try {
+      resource.rollback(xid);
+      fail("Rollback after timeout should have thrown XA_HEURRB");
+    } catch (XAException xae) {
+      assertEquals("Rollback after timeout should have thrown XA_HEURRB",
+                   XAException.XA_HEURRB, xae.errorCode);
+      resource.forget(xid);
+    }
+  }
+
+  /**
+   * Test various operations after a transaction fails.
+   */
+  public void testTransactionFailure() {
+    logger.info("Testing transactionFailure");
+
+    try {
+      // query after failure should fail
+      shouldFailQE(new TestOp() {
+        public void run(Session session, Xid xid) throws Exception {
+          session.query(createQuery(modelURI)).close();
+        }
+      }, "Query in failed transaction did not fail");
+
+      // insert after failure should fail
+      shouldFailQE(new TestOp() {
+        public void run(Session session, Xid xid) throws Exception {
+          session.insert(modelURI, Collections.singleton(new TripleImpl(
+                                                    new URIReferenceImpl(URI.create("test:a")),
+                                                    new URIReferenceImpl(URI.create("test:b")),
+                                                    new URIReferenceImpl(URI.create("test:c")))));
+        }
+      }, "Insert in failed transaction did not fail");
+
+      // start w/o end after failure should fail
+      shouldFailXAErr(new TestOp() {
+        public void run(Session session, Xid xid) throws Exception {
+          session.getXAResource().start(xid, XAResource.TMNOFLAGS);
+        }
+      }, XAException.XAER_DUPID, true, "Start w/o end in failed transaction did not fail");
+
+      shouldFailXAErr(new TestOp() {
+        public void run(Session session, Xid xid) throws Exception {
+          session.getXAResource().start(xid, XAResource.TMRESUME);
+        }
+        // XXX: shouldn't this be XAER_PROTO ?
+      }, XAException.XA_RBROLLBACK, true, "Start w/o end in failed transaction did not fail");
+
+      shouldFailXAErr(new TestOp() {
+        public void run(Session session, Xid xid) throws Exception {
+          session.getXAResource().start(xid, XAResource.TMJOIN);
+        }
+      }, XAException.XAER_PROTO, true, "Start w/o end in failed transaction did not fail");
+
+      // prepare w/o end after failure should fail
+      shouldFailXAErr(new TestOp() {
+        public void run(Session session, Xid xid) throws Exception {
+          session.getXAResource().prepare(xid);
+        }
+        // XXX: shouldn't this be XAER_PROTO ?
+      }, XAException.XA_RBROLLBACK, true, "Prepare w/o end in failed transaction did not fail");
+
+      // commit w/o end after failure should fail
+      shouldFailXAErr(new TestOp() {
+        public void run(Session session, Xid xid) throws Exception {
+          session.getXAResource().commit(xid, true);
+        }
+        // XXX: shouldn't this be XAER_PROTO ?
+      }, XAException.XA_RBROLLBACK, true, "Commit w/o end in failed transaction did not fail");
+
+      shouldFailXAErr(new TestOp() {
+        public void run(Session session, Xid xid) throws Exception {
+          session.getXAResource().commit(xid, false);
+        }
+        // XXX: shouldn't this be XAER_PROTO ?
+      }, XAException.XA_RBROLLBACK, true, "Commit w/o end in failed transaction did not fail");
+
+      // rollback w/o end after failure should fail
+      shouldFailXAErr(new TestOp() {
+        public void run(Session session, Xid xid) throws Exception {
+          session.getXAResource().rollback(xid);
+        }
+        // XXX: shouldn't this be XAER_PROTO ?
+      }, XAException.XA_HEURRB, true, "Rollback w/o end in failed transaction did not fail");
+
+      // prepare after failure should fail
+      shouldFailXAErr(new TestOp() {
+        public void run(Session session, Xid xid) throws Exception {
+          session.getXAResource().prepare(xid);
+        }
+      }, XAException.XA_RBROLLBACK, false, "Prepare in failed transaction did not fail");
+
+      // commit after failure should fail
+      shouldFailXAErr(new TestOp() {
+        public void run(Session session, Xid xid) throws Exception {
+          session.getXAResource().commit(xid, true);
+        }
+      }, XAException.XA_RBROLLBACK, false, "Commit in failed transaction did not fail");
+
+      shouldFailXAErr(new TestOp() {
+        public void run(Session session, Xid xid) throws Exception {
+          session.getXAResource().commit(xid, false);
+        }
+        // XXX: shouldn't this be XAER_PROTO ?
+      }, XAException.XA_RBROLLBACK, false, "Commit w/o prepare in failed transaction did not fail");
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  private void shouldFailQE(TestOp op, String msg) throws Exception {
+    testTransactionFailureOp(op, true, 0, true, msg);
+  }
+
+  private void shouldFailXAErr(TestOp op, int expXAErr, boolean beforeEnd, String msg) throws Exception {
+    testTransactionFailureOp(op, false, expXAErr, beforeEnd, msg);
+  }
+
+  /*
+  private void shouldSucceed(TestOp op) throws Exception {
+    testTransactionFailureOp(op, false, null);
+  }
+  */
+
+  private void testTransactionFailureOp(TestOp op, boolean shouldFailQE, int expXAErr,
+                                        boolean beforeEnd, String msg) throws Exception {
+    Session session = database.newSession();
+    try {
+      boolean shouldFail = shouldFailQE || expXAErr != 0;
+
+      // start tx
+      XAResource resource = session.getXAResource();
+
+      Xid xid = new TestXid(1);
+      resource.start(xid, XAResource.TMNOFLAGS);
+
+      // run bad query -> failed tx
+      try {
+        session.query(createQuery(URI.create("urn:no:such:model")));
+        fail("Bad query failed to throw exception");
+      } catch (QueryException qe) {
+      }
+
+      // run test op, verify it succeeds/fails, and reset
+      if (!beforeEnd) {
+        try {
+          resource.end(xid, XAResource.TMSUCCESS);
+        } catch (XAException xae) {
+          if (!isRollback(xae))
+            throw xae;
+        }
+      }
+
+      try {
+        op.run(session, xid);
+        if (shouldFail)
+          fail(msg);
+      } catch (QueryException qe) {
+        if (!shouldFailQE)
+          throw qe;
+      } catch (XAException xae) {
+        assertTrue(msg + ": " + xae.errorCode,
+                   xae.errorCode == expXAErr || xae.errorCode == XAException.XAER_NOTA);
+      }
+
+      try {
+        if (beforeEnd) {
+          try {
+            resource.end(xid, XAResource.TMSUCCESS);
+          } catch (XAException xae) {
+            if (!isRollback(xae))
+              throw xae;
+          }
+        }
+
+        try {
+          resource.rollback(xid);
+        } catch (XAException xae) {
+          if (xae.errorCode == XAException.XA_HEURRB)
+            resource.forget(xid);
+          else if (!isRollback(xae))
+            throw xae;
+        }
+      } catch (XAException xae) {
+        if (!shouldFail || xae.errorCode != XAException.XAER_NOTA)
+          throw xae;
+      }
+
+      // verify we're good to go
+      resource.start(xid, XAResource.TMNOFLAGS);
+      session.query(createQuery(modelURI)).close();
+      resource.end(xid, XAResource.TMSUCCESS);
+      resource.commit(xid, true);
+    } finally {
+      session.close();
+    }
+  }
+
+  private static boolean isRollback(XAException xae) {
+    return xae.errorCode >= XAException.XA_RBBASE && xae.errorCode <= XAException.XA_RBEND;
+  }
+
+  private static interface TestOp {
+    public void run(Session session, Xid xid) throws Exception;
+  }
+
+
+  /**
+   * Test session close on still-active session.
+   */
+  public void testSessionClose() {
+    logger.info("Testing sessionClose");
+
+    // test close while waiting for write-lock
+    try {
+      Session session1 = database.newSession();
+
+      XAResource resource1 = session1.getXAResource();
+      Xid xid1 = new TestXid(1);
+      resource1.start(xid1, XAResource.TMNOFLAGS);
+
+      try {
+        final Session session2 = database.newSession();
+
+        Thread t1 = new Thread("closeTest") {
+          public void run() {
+            try {
+              XAResource resource2 = session2.getXAResource();
+              Xid xid2 = new TestXid(2);
+              resource2.start(xid2, XAResource.TMNOFLAGS);
+
+              fail("Acquired write-lock unexpectedly");
+            } catch (XAException xae) {
+              logger.debug("Caught expected exception", xae);
+            } catch (QueryException qe) {
+              logger.error("Caught unexpected exception", qe);
+              fail("Caught unexpected exception " + qe);
+            } finally {
+              try {
+                session2.close();
+              } catch (QueryException qe) {
+                logger.debug("Caught expected exception", qe);
+              }
+            }
+          }
+        };
+        t1.start();
+        Thread.sleep(100L);     // give thread some time to start and block
+
+        assertTrue("second session should still be active", t1.isAlive());
+
+        session2.close();
+
+        try {
+          t1.join(100L);
+        } catch (InterruptedException ie) {
+          logger.error("wait for thread-termination interrupted", ie);
+          fail(ie);
+        }
+        assertFalse("second session should've terminated", t1.isAlive());
+
+      } finally {
+        logger.debug("closing session1");
+        resource1.end(xid1, XAResource.TMSUCCESS);
+        resource1.commit(xid1, true);
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
   //
   // Internal methods
   //
 
+  private Query createQuery(URI model) {
+    Variable subjectVariable   = new Variable("subject");
+    Variable predicateVariable = new Variable("predicate");
+    Variable objectVariable    = new Variable("object");
+
+    List<SelectElement> selectList = new ArrayList<SelectElement>(3);
+    selectList.add(subjectVariable);
+    selectList.add(predicateVariable);
+    selectList.add(objectVariable);
+
+    return new Query(
+      selectList,                                       // SELECT
+      new ModelResource(model),                         // 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
+    );
+  }
+
+  private String[][] expectedResults() {
+    return new String[][] {
+          { "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" },
+        };
+  }
+
   private void compareResults(String[][] expected, Answer answer) throws Exception {
     try {
       answer.beforeFirst();

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/GlobalizedAnswer.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/GlobalizedAnswer.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/GlobalizedAnswer.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -35,7 +35,6 @@
 
 // Locally written packages
 import org.mulgara.query.Answer;
-import org.mulgara.query.QueryException;
 import org.mulgara.query.TuplesException;
 import org.mulgara.query.Variable;
 import org.mulgara.resolver.spi.GlobalizeException;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -31,8 +31,6 @@
 // Java 2 standard packages
 import javax.transaction.xa.XAResource;
 import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Set;
 
 // Third party packages
 import org.apache.log4j.Logger;  // Apache Log4J
@@ -40,12 +38,10 @@
 // Local packages
 import org.mulgara.query.*;
 import org.mulgara.query.rdf.URIReferenceImpl;
-import org.mulgara.resolver.spi.DummyXAResource;
 import org.mulgara.resolver.spi.LocalizeException;
 import org.mulgara.resolver.spi.Resolution;
 import org.mulgara.resolver.spi.Resolver;
 import org.mulgara.resolver.spi.ResolverException;
-import org.mulgara.resolver.spi.ResolverFactory;
 import org.mulgara.resolver.spi.ResolverFactoryException;
 import org.mulgara.resolver.spi.ResolverSession;
 import org.mulgara.resolver.spi.SingletonStatements;
@@ -53,7 +49,6 @@
 import org.mulgara.resolver.view.SessionView;
 import org.mulgara.resolver.view.ViewMarker;
 import org.mulgara.store.nodepool.NodePool;
-import org.mulgara.store.tuples.Tuples;
 
 /**
  * Access to models that appear in the system model (<code>#</code>).

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolverFactory.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolverFactory.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolverFactory.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -34,7 +34,6 @@
 import org.mulgara.resolver.spi.ResolverFactory;
 import org.mulgara.resolver.spi.ResolverFactoryException;
 import org.mulgara.resolver.spi.ResolverSession;
-import org.mulgara.store.nodepool.NodePool;
 
 /**
  * Wrapper around an external {@link ResolverFactory} that applies caching to

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/JRDFResolverSession.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/JRDFResolverSession.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/JRDFResolverSession.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -30,7 +30,6 @@
 // Java 2 standard packages
 import java.net.URI;
 import java.util.Set;
-import java.io.File;
 
 // Third party packages
 import org.apache.log4j.Logger;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/JRDFResolverSessionFactory.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/JRDFResolverSessionFactory.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/JRDFResolverSessionFactory.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -39,7 +39,6 @@
 // Local packages
 import org.mulgara.resolver.spi.*;
 import org.mulgara.store.nodepool.NodePoolException;
-import org.mulgara.store.stringpool.StringPool;
 import org.mulgara.store.stringpool.StringPoolException;
 import org.mulgara.store.xa.SimpleXARecoveryHandler;
 import org.mulgara.store.xa.SimpleXAResourceException;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -46,16 +46,10 @@
 
 // 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;
 
@@ -250,47 +244,9 @@
       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 answer = session.query(createQuery(modelURI));
+        compareResults(expectedResults(), answer);
         answer.close();
 
         txManager.commit();
@@ -314,47 +270,9 @@
         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 answer = session.query(createQuery(modelURI));
+        compareResults(expectedResults(), answer);
         answer.close();
 
         txManager.commit();
@@ -375,45 +293,10 @@
       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 answer1 = session.query(createQuery(modelURI));
 
-        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
-        ));
+        Answer answer2 = session.query(createQuery(modelURI));
 
         compareResults(answer1, answer2);
 
@@ -438,47 +321,9 @@
       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 answer = session.query(createQuery(modelURI));
+        compareResults(expectedResults(), answer);
         answer.close();
 
         txManager.commit();
@@ -501,51 +346,16 @@
       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
-        ));
-        
+        Answer answer1 = session.query(createQuery(modelURI));
+
         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
-        ));
+        Answer answer2 = session.query(createQuery(modelURI));
 
         tx2 = txManager.suspend();
 
@@ -576,51 +386,16 @@
       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
-        ));
-        
+        Answer answer1 = session.query(createQuery(modelURI));
+
         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
-        ));
+        Answer answer2 = session.query(createQuery(modelURI));
 
         tx2 = txManager.suspend();
 
@@ -660,35 +435,11 @@
       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
-        ));
+        Answer answer = session.query(createQuery(modelURI));
 
         Transaction tx2 = txManager.suspend();
 
@@ -709,40 +460,11 @@
         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
-        ));
+        Answer answer2 = session.query(createQuery(model2URI));
 
         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);
+        compareResults(expectedResults(), answer2);
         answer2.close();
 
         txManager.begin();
@@ -1185,78 +907,16 @@
   }
 
   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
-    ));
+    Answer answer = session.query(createQuery(model3URI));
 
-    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);
+    compareResults(expectedResults(), 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 answer = session.query(createQuery(model3URI));
     answer.beforeFirst();
     assertFalse(answer.next());
     answer.close();
@@ -1298,48 +958,10 @@
                   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 answer = session2.query(createQuery(model3URI));
 
-                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);
+                compareResults(expectedResults(), answer);
                 answer.close();
 
                 resource2.end(new TestXid(3), XAResource.TMSUCCESS);
@@ -1436,48 +1058,10 @@
                   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 answer = session2.query(createQuery(model3URI));
 
-                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);
+                compareResults(expectedResults(), answer);
                 answer.close();
 
                 session2.setAutoCommit(true);
@@ -1571,48 +1155,10 @@
                   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 answer = session2.query(createQuery(model3URI));
 
-                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);
+                compareResults(expectedResults(), answer);
                 answer.close();
 
                 resource.end(new TestXid(1), XAResource.TMSUCCESS);
@@ -1719,32 +1265,8 @@
                   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 answer = session2.query(createQuery(model3URI));
 
                 answer.beforeFirst();
                 assertFalse(answer.next());
@@ -1841,32 +1363,8 @@
                   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 answer = session2.query(createQuery(model3URI));
 
                 answer.beforeFirst();
                 assertFalse(answer.next());
@@ -1962,32 +1460,8 @@
 
         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 answer = session.query(createQuery(model3URI));
         answer.beforeFirst();
         assertFalse(answer.next());
         answer.close();
@@ -2005,22 +1479,7 @@
         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 = session.query(createQuery(model3URI));
 
         answer.beforeFirst();
         assertFalse(answer.next());
@@ -2057,6 +1516,51 @@
   // Internal methods
   //
 
+  private Query createQuery(URI model) {
+    Variable subjectVariable   = new Variable("subject");
+    Variable predicateVariable = new Variable("predicate");
+    Variable objectVariable    = new Variable("object");
+
+    List<SelectElement> selectList = new ArrayList<SelectElement>(3);
+    selectList.add(subjectVariable);
+    selectList.add(predicateVariable);
+    selectList.add(objectVariable);
+
+    return new Query(
+      selectList,                                       // SELECT
+      new ModelResource(model),                         // 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
+    );
+  }
+
+  private String[][] expectedResults() {
+    return new String[][] {
+          { "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" },
+        };
+  }
+
   private void compareResults(String[][] expected, Answer answer) throws Exception {
     try {
       answer.beforeFirst();

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -30,16 +30,8 @@
 // Java 2 standard packages
 import java.io.*;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.*;
 
-import javax.transaction.xa.XAResource;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-// Java 2 enterprise packages
-import javax.transaction.TransactionManager;
-
 // Third party packages
 import org.apache.log4j.Logger;
 import org.jrdf.graph.*;
@@ -53,6 +45,7 @@
 import org.mulgara.resolver.spi.*;
 import org.mulgara.server.*;
 import org.mulgara.store.statement.StatementStore;
+import org.mulgara.transaction.TransactionManagerFactory;
 
 /**
  * A JRDF database session.
@@ -123,6 +116,7 @@
    * @throws IllegalArgumentException if any argument is <code>null</code>
    */
   LocalJRDFDatabaseSession(MulgaraTransactionManager transactionManager, 
+      TransactionManagerFactory transactionManagerFactory,
       List securityAdapterList, List symbolicTransformationList,
       ResolverSessionFactory resolverSessionFactory,
       SystemResolverFactory systemResolverFactory,
@@ -131,8 +125,8 @@
       DatabaseMetadata metadata, ContentHandlerManager contentHandlers,
       Set cachedResolverFactorySet, URI temporaryModelTypeURI)
       throws ResolverFactoryException {
-    super(transactionManager, securityAdapterList, symbolicTransformationList,
-        resolverSessionFactory,
+    super(transactionManager, transactionManagerFactory, securityAdapterList,
+        symbolicTransformationList, resolverSessionFactory,
         systemResolverFactory, temporaryResolverFactory, resolverFactoryList,
         externalResolverFactoryMap, internalResolverFactoryMap, metadata,
         contentHandlers, cachedResolverFactorySet, temporaryModelTypeURI);

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/LoginOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/LoginOperation.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/LoginOperation.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -36,7 +36,6 @@
 
 // Local packages
 import org.mulgara.resolver.spi.*;
-import org.mulgara.server.Session;
 
 /**
  * An {@link Operation} that implements the {@link Session#login} method.

Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MockResolver.java (from rev 1097, branches/mgr-121-lockrecovery/src/jar/resolver/java/org/mulgara/resolver/MockResolver.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MockResolver.java	                        (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MockResolver.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -0,0 +1,183 @@
+/*
+ * 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.net.URI;
+
+// Third party packages
+import javax.transaction.xa.XAResource;
+
+import org.apache.log4j.Logger;
+import org.jrdf.graph.Node;
+import org.jrdf.graph.URIReference;
+
+// Locally written packages
+import org.mulgara.query.Constraint;
+import org.mulgara.query.LocalNode;
+import org.mulgara.query.QueryException;
+import org.mulgara.query.TuplesException;
+import org.mulgara.resolver.spi.DummyXAResource;
+import org.mulgara.resolver.spi.EmptyResolution;
+import org.mulgara.resolver.spi.GlobalizeException;
+import org.mulgara.resolver.spi.Resolution;
+import org.mulgara.resolver.spi.Resolver;
+import org.mulgara.resolver.spi.ResolverException;
+import org.mulgara.resolver.spi.ResolverSession;
+import org.mulgara.resolver.spi.Statements;
+
+/**
+ * Simple mock resolver. All operations are dummys: model creation, removing, and modifying are
+ * no-ops, and queries return an empty resolution. The model name is parsed for commands to
+ * execute: the model must have a query string consisting of an '&amp;' separated list of commands
+ * where each command is of the form "name=value" (the value may be empty) (i.e. follow http
+ * query string syntax for parameters).
+ *
+ * <p>Currently the only commands supported: are 'active', 'wait', and 'hardWait':
+ * <dl>
+ *   <dt>active</dt>
+ *   <dd>the value must be any combination of the characters 'c', 'd', 'm', 'r', 'f', 'n', or
+ *       'l', which stand for 'Resolver.createModel', 'Resolver.dropModel', 'Resolver.modifyModel',
+ *       'Resolver.resolve', 'Answer.beforeFirst', 'Answer.next', and 'Answer.cLose', respectively.
+ *       The commands after this one are only executed for these operations. If not specified, the
+ *       default is 'cdmrfnl' (i.e. all operations)</dd>
+ *   <dt>wait</dt>
+ *   <dd>the value is the number of milliseconds to sleep before continuing.</dd>
+ *   <dt>hardWait</dt>
+ *   <dd>like <var>wait</var>, but ignore interrupts.</dd>
+ * </dl>
+ *
+ * @created 2009-07-05
+ * @author Ronald Tschalär
+ * @copyright &copy;2008 <a href="http://www.topazproject.org/">Topaz Foundation</a>
+ * @licence Apache License v2.0
+ */
+public class MockResolver implements Resolver {
+  /** Logger */
+  private static Logger logger = Logger.getLogger(MockResolver.class);
+
+  /** The session that this resolver is associated with */
+  private final ResolverSession resolverSession;
+
+  MockResolver(ResolverSession resolverSession) {
+    this.resolverSession = resolverSession;
+  }
+
+  public void createModel(long model, URI modelTypeURI) throws ResolverException {
+    processCommands(model, 'c', ResolverException.class);
+  }
+
+  public XAResource getXAResource() {
+    return new DummyXAResource(10);
+  }
+
+  public void modifyModel(long model, Statements statements, boolean occurs) throws ResolverException {
+    processCommands(model, 'm', ResolverException.class);
+  }
+
+  public void removeModel(long model) throws ResolverException {
+    processCommands(model, 'd', ResolverException.class);
+  }
+
+  public Resolution resolve(Constraint constraint) throws QueryException {
+    long model = ((LocalNode) constraint.getModel()).getValue();
+    processCommands(model, 'r', QueryException.class);
+    return new MockEmptyResolution(constraint, model);
+  }
+
+  public void abort() {}
+
+  private <T extends Throwable> void processCommands(long model, char op, Class<T> exc) throws T {
+    URI modelUri = toURI(model, exc);
+    logger.debug("model-uri='" + modelUri + "', op='" + op + "'");
+
+    String query = modelUri.getQuery();
+    if (query == null) {
+      logger.debug("no query found, no commands");
+      return;
+    }
+
+    for (String param : query.split("&")) {
+      String name  = param.substring(0, param.indexOf('='));
+      String value = param.substring(param.indexOf('=') + 1);
+
+      logger.debug("processing command '" + name + "' with value '" + value + "'");
+
+      if (name.equals("active")) {
+        if (value.indexOf(op) < 0) {
+          break;
+        }
+      } else if (name.equals("wait")) {
+        logger.debug("sleeping '" + value + "' milliseconds");
+        try {
+          Thread.sleep(Long.parseLong(value));
+        } catch (InterruptedException ie) {
+          throw MulgaraTransactionFactory.newExceptionOrCause(exc, "sleep interrupted", ie);
+        }
+      } else if (name.equals("hardWait")) {
+        logger.debug("sleeping '" + value + "' milliseconds");
+        long targetDate = System.currentTimeMillis() + Long.parseLong(value);
+        while (true) {
+          long wait = targetDate - System.currentTimeMillis();
+          if (wait <= 0)
+            break;
+          try {
+            Thread.sleep(wait);
+          } catch (InterruptedException ie) {
+          }
+        }
+      } else {
+        logger.info("Unknown command '" + name + "' - ignoring");
+      }
+    }
+  }
+
+  private <T extends Throwable> URI toURI(long model, Class<T> exc) throws T {
+    try {
+      Node globalModel = resolverSession.globalize(model);
+      return ((URIReference) globalModel).getURI();
+    } catch (GlobalizeException ge) {
+      throw MulgaraTransactionFactory.newExceptionOrCause(exc, "Couldn't globalize model", ge);
+    }
+  }
+
+  private class MockEmptyResolution extends EmptyResolution {
+    private final long model;
+
+    public MockEmptyResolution(Constraint constraint, long model) {
+      super(constraint, true);
+      this.model = model;
+    }
+
+    public void beforeFirst(long[] prefix, int suffixTruncation) throws TuplesException {
+      processCommands(model, 'f', TuplesException.class);
+      super.beforeFirst(prefix, suffixTruncation);
+    }
+
+    public boolean next() throws TuplesException {
+      processCommands(model, 'n', TuplesException.class);
+      return super.next();
+    }
+
+    public void close() {
+      processCommands(model, 'l', RuntimeException.class);
+      super.close();
+    }
+  }
+}

Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MockResolverFactory.java (from rev 1097, branches/mgr-121-lockrecovery/src/jar/resolver/java/org/mulgara/resolver/MockResolverFactory.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MockResolverFactory.java	                        (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MockResolverFactory.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -0,0 +1,67 @@
+/*
+ * 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.net.URI;
+
+// Locally written packages
+import org.mulgara.query.rdf.Mulgara;
+import org.mulgara.query.rdf.URIReferenceImpl;
+import org.mulgara.resolver.spi.InitializerException;
+import org.mulgara.resolver.spi.Resolver;
+import org.mulgara.resolver.spi.ResolverException;
+import org.mulgara.resolver.spi.ResolverFactory;
+import org.mulgara.resolver.spi.ResolverFactory.Graph;
+import org.mulgara.resolver.spi.ResolverFactoryException;
+import org.mulgara.resolver.spi.ResolverFactoryInitializer;
+import org.mulgara.resolver.spi.ResolverSession;
+
+/**
+ * Factory for simple mock resolver.
+ *
+ * @created 2009-07-05
+ * @author Ronald Tschalär
+ * @copyright &copy;2008 <a href="http://www.topazproject.org/">Topaz Foundation</a>
+ * @licence Apache License v2.0
+ */
+public class MockResolverFactory implements ResolverFactory {
+  private static URI modelTypeURI = URI.create(Mulgara.NAMESPACE + "MockModel");
+
+  private MockResolverFactory(ResolverFactoryInitializer resolverFactoryInitializer)
+      throws ResolverException, InitializerException {
+    resolverFactoryInitializer.preallocate(new URIReferenceImpl(modelTypeURI));
+    resolverFactoryInitializer.addModelType(modelTypeURI, this);
+  }
+
+  public void close() { }
+  public void delete() { }
+  public Graph[] getDefaultGraphs() { return null; }
+  public boolean supportsExport() { return true; }
+
+  public static ResolverFactory newInstance(ResolverFactoryInitializer resolverFactoryInitializer)
+      throws ResolverException, InitializerException {
+    return new MockResolverFactory(resolverFactoryInitializer);
+  }
+
+  public Resolver newResolver(boolean canWrite, ResolverSession resolverSession, Resolver systemResolver)
+      throws ResolverFactoryException {
+    return new MockResolver(resolverSession);
+  }
+}

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/ModelExistsOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/ModelExistsOperation.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/ModelExistsOperation.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -41,7 +41,6 @@
 import org.mulgara.query.*;
 import org.mulgara.query.rdf.URIReferenceImpl;
 import org.mulgara.resolver.spi.*;
-import org.mulgara.server.Session;
 
 class ModelExistsOperation implements Operation
 {

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -39,7 +39,6 @@
 import org.mulgara.query.*;
 import org.mulgara.query.rdf.URIReferenceImpl;
 import org.mulgara.resolver.spi.*;
-import org.mulgara.server.Session;
 
 /**
  * An {@link Operation} that implements the {@link Session#insert} and

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -32,7 +32,6 @@
 // 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;
@@ -65,9 +64,14 @@
   private MulgaraExternalTransactionFactory factory;
   private DatabaseOperationContext context;
 
+  private boolean inXACompletion;
+
   private boolean hRollback;
   private int heurCode;
   private boolean rollback;
+  private String rollbackCause;
+  private boolean completed;
+  private volatile long lastActive;
 
   MulgaraExternalTransaction(MulgaraExternalTransactionFactory factory, Xid xid, DatabaseOperationContext context)
       throws QueryException {
@@ -82,9 +86,13 @@
 
     this.xaResources = new HashMap<EnlistableResource, XAResource>();
 
+    this.inXACompletion = false;
+
     this.hRollback = false;
     this.heurCode = 0;
     this.rollback = false;
+    this.completed = false;
+    this.lastActive = System.currentTimeMillis();
 
     this.context.initiate(this);
   }
@@ -107,102 +115,196 @@
   public MulgaraTransactionException abortTransaction(String errorMessage, Throwable cause)
       throws MulgaraTransactionException {
     report("abortTransaction");
+
+    // we should actually already have the mutex, but let's make sure
+    acquireMutex(0L, true, MulgaraTransactionException.class);
     try {
-      for (EnlistableResource resource : enlisted) {
-        try {
-          resource.abort();
-        } catch (Throwable throw_away) {}
+      if (rollbackCause == null)
+        rollbackCause = errorMessage;
+
+      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 {
+        completed = true;
+        factory.transactionComplete(this);
       }
-      for (EnlistableResource resource : prepared) {
-        try {
-          resource.abort();
-        } catch (Throwable throw_away) {}
-      }
-
-      return new MulgaraTransactionException(errorMessage, cause);
     } finally {
-      factory.transactionComplete(this);
+      releaseMutex();
     }
   }
 
   public void heuristicRollback(String cause) throws MulgaraTransactionException {
-    report("heuristicRollback");
-    hRollback = true;
+    report("heuristicRollback: " + cause);
+
+    synchronized (factory.getMutexLock()) {
+      if (factory.getMutexHolder() != null && factory.getMutexHolder() != Thread.currentThread()) {
+        if (inXACompletion) {
+          return;       // this txn is already being cleaned up, so let it go
+        }
+      }
+
+      factory.acquireMutexWithInterrupt(0L, MulgaraTransactionException.class);
+      inXACompletion = true;
+    }
+
     try {
-      rollback(xid);
-    } catch (XAException xa) {
-      throw new MulgaraTransactionException("Failed heuristic rollback", xa);
+      if (hRollback)
+        return;
+      hRollback = true;
+
+      if (rollbackCause == null)
+        rollbackCause = cause;
+
+      try {
+        rollback(xid);
+      } catch (XAException xa) {
+        throw new MulgaraTransactionException("Failed heuristic rollback", xa);
+      } finally {
+        heurCode = heurCode == 0 ? XAException.XA_HEURRB : heurCode;
+      }
     } finally {
-      heurCode = heurCode == 0 ? XAException.XA_HEURRB : heurCode;
+      releaseMutex();
     }
   }
 
   public void execute(Operation operation, DatabaseMetadata metadata) throws MulgaraTransactionException {
+    acquireMutex(0, false, MulgaraTransactionException.class);
     try {
-      operation.execute(context,
-                        context.getSystemResolver(),
-                        metadata);
-    } catch (Throwable th) {
+      checkActive(MulgaraTransactionException.class);
       try {
-        rollback(xid);
-      } catch (XAException ex) {
-        logger.error("Error in rollback after operation failure", ex);
+        long la = lastActive;
+        lastActive = -1;
+
+        operation.execute(context,
+            context.getSystemResolver(),
+            metadata);
+
+        lastActive = (la != -1) ? System.currentTimeMillis() : -1;
+      } catch (Throwable th) {
+        try {
+          heuristicRollback(th.toString());
+        } catch (MulgaraTransactionException ex) {
+          logger.error("Error in rollback after operation failure", ex);
+        }
+        throw new MulgaraTransactionException("Operation failed", th);
       }
-      throw new MulgaraTransactionException("Operation failed", th);
+    } finally {
+      releaseMutex();
     }
   }
 
   public AnswerOperationResult execute(AnswerOperation ao) throws TuplesException {
+    acquireMutex(0, false, TuplesException.class);
     try {
-      ao.execute();
-      return ao.getResult();
-    } catch (Throwable th) {
+      checkActive(TuplesException.class);
       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);
+        long la = lastActive;
+        lastActive = -1;
+
+        ao.execute();
+
+        lastActive = (la != -1) ? System.currentTimeMillis() : -1;
+
+        return ao.getResult();
+      } catch (Throwable th) {
+        try {
+          logger.warn("Error in answer operation triggered rollback", th);
+          heuristicRollback(th.toString());
+        } catch (MulgaraTransactionException ex) {
+          logger.error("Error in rollback after answer-operation failure", ex);
+        }
+        throw new TuplesException("Request failed", th);
       }
-      throw new TuplesException("Request failed", th);
+    } finally {
+      releaseMutex();
     }
   }
 
   // FIXME: See if we can't rearrange things to allow this to be deleted.
   public void execute(TransactionOperation to) throws MulgaraTransactionException {
-    to.execute();
+    acquireMutex(0, false, MulgaraTransactionException.class);
+    try {
+      checkActive(MulgaraTransactionException.class);
+
+      long la = lastActive;
+      lastActive = -1;
+
+      to.execute();
+
+      lastActive = (la != -1) ? System.currentTimeMillis() : -1;
+    } finally {
+      releaseMutex();
+    }
   }
 
+  private <T extends Throwable> void checkActive(Class<T> exc) throws T {
+    if (hRollback)
+      throw factory.newException(exc, "Transaction was heuristically rolled back. Reason: " + rollbackCause);
+    if (rollback)
+      throw factory.newException(exc, "Transaction was rolled back. Reason: " + rollbackCause);
+    if (completed)
+      throw factory.newException(exc, "Transaction has been completed");
+  }
+
   public void enlist(EnlistableResource enlistable) throws MulgaraTransactionException {
+    acquireMutex(0, false, MulgaraTransactionException.class);
     try {
-      XAResource res = enlistable.getXAResource();
-      for (EnlistableResource eres : enlisted) {
-        if (res.isSameRM(xaResources.get(eres))) {
-          return;
+      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);
       }
-      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);
+    } finally {
+      releaseMutex();
     }
   }
 
+  public long lastActive() {
+    return lastActive;
+  }
+
   //
   // 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);
+
+    acquireMutex(0, true, XAException.class);
+    try {
+      lastActive = -1;
+
+      // FIXME: Consider the possiblity prepare failed, or was incomplete.
+      for (EnlistableResource er : prepared) {
+        xaResources.get(er).commit(xid, false);
+        committed.add(er);
+      }
+      cleanupTransaction();
+    } finally {
+      releaseMutex();
     }
-    cleanupTransaction();
   }
 
   boolean isHeuristicallyRollbacked() {
@@ -223,11 +325,20 @@
 
   void prepare(Xid xid) throws XAException {
     report("prepare");
-    for (EnlistableResource er : enlisted) {
-      xaResources.get(er).prepare(xid);
-      prepared.add(er);
+
+    acquireMutex(0, true, XAException.class);
+    try {
+      long la = lastActive;
+      lastActive = -1;
+
+      for (EnlistableResource er : enlisted) {
+        xaResources.get(er).prepare(xid);
+        prepared.add(er);
+      }
+      lastActive = (la != -1) ? System.currentTimeMillis() : -1;
+    } finally {
+      releaseMutex();
     }
-    // status = PREPARED; ?
   }
 
   /**
@@ -236,77 +347,84 @@
    */
   void rollback(Xid xid) throws XAException {
     report("rollback");
+
+    acquireMutex(0, true, XAException.class);
     try {
-      rollback = true;
-      Map<EnlistableResource, XAException> rollbackFailed = new HashMap<EnlistableResource, XAException>();
+      lastActive = -1;
+      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);
+        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);
           }
-        } 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.
+        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;
-              } 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);
+                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);
         }
-
-        throw new XAException(heurCode);
+      } finally {
+        cleanupTransaction();
       }
     } finally {
-      cleanupTransaction();
+      releaseMutex();
     }
   }
 
@@ -328,9 +446,22 @@
         logger.error("Failed to abort transaction on cleanup failure", em2);
         throw new XAException(XAException.XAER_RMFAIL);
       }
+    } finally {
+      completed = true;
     }
   }
 
+  private <T extends Throwable> void acquireMutex(long timeout, boolean isXACompletion, Class<T> exc) throws T {
+    synchronized (factory.getMutexLock()) {
+      factory.acquireMutex(timeout, exc);
+      inXACompletion = isXACompletion;
+    }
+  }
+
+  private void releaseMutex() {
+    factory.releaseMutex();
+  }
+
   private void report(String desc) {
     if (logger.isInfoEnabled()) {
       logger.info(desc + ": " + System.identityHashCode(this));

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransactionFactory.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransactionFactory.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransactionFactory.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -19,9 +19,7 @@
 package org.mulgara.resolver;
 
 // Java2 packages
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.HashSet;
 import java.util.Set;
 import javax.transaction.xa.XAResource;
 import javax.transaction.xa.Xid;
@@ -29,7 +27,6 @@
 // Local packages
 import org.mulgara.query.MulgaraTransactionException;
 import org.mulgara.query.QueryException;
-import org.mulgara.util.Assoc1toNMap;
 
 /**
  * Manages external transactions.
@@ -46,41 +43,40 @@
  */
 
 public class MulgaraExternalTransactionFactory extends MulgaraTransactionFactory {
-  private Map<DatabaseSession, MulgaraExternalTransaction> associatedTransaction;
-  private Assoc1toNMap<DatabaseSession, MulgaraExternalTransaction> sessionXAMap;
+  private final Set<MulgaraExternalTransaction> transactions;
 
-  private Map<DatabaseSession, MulgaraXAResourceContext> xaResources;
+  private final MulgaraXAResourceContext xaResource;
 
-  public MulgaraExternalTransactionFactory(MulgaraTransactionManager manager) {
-    super(manager);
+  private MulgaraExternalTransaction associatedTransaction;
 
-    this.associatedTransaction = new HashMap<DatabaseSession, MulgaraExternalTransaction>();
-    this.sessionXAMap = new Assoc1toNMap<DatabaseSession, MulgaraExternalTransaction>();
-    this.xaResources = new HashMap<DatabaseSession, MulgaraXAResourceContext>();
+  public MulgaraExternalTransactionFactory(DatabaseSession session, MulgaraTransactionManager manager) {
+    super(session, manager);
+
+    this.associatedTransaction = null;
+    this.transactions = new HashSet<MulgaraExternalTransaction>();
+    this.xaResource = new MulgaraXAResourceContext(this, session);
   }
 
-  public MulgaraTransaction getTransaction(final DatabaseSession session, boolean write)
-      throws MulgaraTransactionException {
-    acquireMutex();
+  public MulgaraTransaction getTransaction(boolean write) throws MulgaraTransactionException {
+    acquireMutex(0, MulgaraTransactionException.class);
     try {
-      MulgaraExternalTransaction xa = associatedTransaction.get(session);
-      if (xa == null) {
+      if (associatedTransaction == null) {
         throw new MulgaraTransactionException("No externally mediated transaction associated with session");
-      } else if (write && xa != writeTransaction) {
+      } else if (write && associatedTransaction != writeTransaction) {
         throw new MulgaraTransactionException("RO-transaction associated with session when requesting write operation");
       }
 
-      return xa;
+      return associatedTransaction;
     } finally {
       releaseMutex();
     }
   }
 
-  protected MulgaraExternalTransaction createTransaction(final DatabaseSession session, Xid xid, boolean write)
+  protected MulgaraExternalTransaction createTransaction(Xid xid, boolean write)
       throws MulgaraTransactionException {
-    acquireMutex();
+    acquireMutex(0, MulgaraTransactionException.class);
     try {
-      if (associatedTransaction.get(session) != null) {
+      if (associatedTransaction != null) {
         throw new MulgaraTransactionException(
             "Attempt to initiate transaction with existing transaction active with session");
       }
@@ -89,27 +85,28 @@
       }
 
       if (write) {
-          runWithoutMutex(new TransactionOperation() {
-            public void execute() throws MulgaraTransactionException {
-              manager.obtainWriteLock(session);
-            }
-          });
+        manager.obtainWriteLock(session);
+        MulgaraExternalTransaction xa = null;
         try {
-          MulgaraExternalTransaction xa = new MulgaraExternalTransaction(this, xid, session.newOperationContext(true));
+          xa = new MulgaraExternalTransaction(this, xid, session.newOperationContext(true));
           writeTransaction = xa;
-          associatedTransaction.put(session, xa);
-          sessionXAMap.put(session, xa);
+          associatedTransaction = xa;
+          transactions.add(xa);
+          transactionCreated(xa);
 
           return xa;
         } catch (Throwable th) {
           manager.releaseWriteLock(session);
+          if (xa != null)
+            transactionComplete(xa);
           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);
+          associatedTransaction = xa;
+          transactions.add(xa);
+          transactionCreated(xa);
 
           return xa;
         } catch (QueryException eq) {
@@ -121,81 +118,57 @@
     }
   }
 
-  public Set<MulgaraExternalTransaction> getTransactionsForSession(DatabaseSession session) {
-    acquireMutex();
-    try {
-      Set<MulgaraExternalTransaction> xas = sessionXAMap.getN(session);
-      return xas != null ? xas : Collections.<MulgaraExternalTransaction>emptySet();
-    } finally {
-      releaseMutex();
-    }
+  protected Set<MulgaraExternalTransaction> getTransactions() {
+    return transactions;
   }
 
-  public XAResource getXAResource(DatabaseSession session, boolean writing) {
-    acquireMutex();
+  public XAResource getXAResource(boolean writing) {
+    acquireMutex(0, RuntimeException.class);
     try {
-      MulgaraXAResourceContext xarc = xaResources.get(session);
-      if (xarc == null) {
-        xarc = new MulgaraXAResourceContext(this, session);
-        xaResources.put(session, xarc);
-      }
-
-      return xarc.getResource(writing);
+      return xaResource.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();
+    acquireMutex(0, MulgaraTransactionException.class);
     try {
+      super.transactionComplete(xa);
+
       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);
+      transactions.remove(xa);
+      if (associatedTransaction == xa) {
+        associatedTransaction = null;
       }
     } finally {
       releaseMutex();
     }
   }
 
-  public boolean hasAssociatedTransaction(DatabaseSession session) {
-    acquireMutex();
+  public boolean hasAssociatedTransaction() {
+    acquireMutex(0, RuntimeException.class);
     try {
-      return associatedTransaction.get(session) != null;
+      return associatedTransaction != null;
     } finally {
       releaseMutex();
     }
   }
 
-  public boolean associateTransaction(DatabaseSession session, MulgaraExternalTransaction xa) {
-    acquireMutex();
+  public boolean associateTransaction(MulgaraExternalTransaction xa) {
+    acquireMutex(0, RuntimeException.class);
     try {
-      if (associatedTransaction.get(session) != null) {
+      if (associatedTransaction != null) {
         return false;
       } else {
-        associatedTransaction.put(session, xa);
+        associatedTransaction = xa;
         return true;
       }
     } finally {
@@ -203,33 +176,34 @@
     }
   }
 
-  public MulgaraExternalTransaction getAssociatedTransaction(DatabaseSession session) {
-    acquireMutex();
+  public MulgaraExternalTransaction getAssociatedTransaction() {
+    acquireMutex(0, RuntimeException.class);
     try {
-      return associatedTransaction.get(session);
+      return associatedTransaction;
     } finally {
       releaseMutex();
     }
   }
 
-  public void disassociateTransaction(DatabaseSession session, MulgaraExternalTransaction xa) 
+  public void disassociateTransaction(MulgaraExternalTransaction xa) 
       throws MulgaraTransactionException {
-    acquireMutex();
+    acquireMutex(0, MulgaraTransactionException.class);
     try {
-      if (associatedTransaction.get(session) == xa) {
-        associatedTransaction.remove(session);
+      if (associatedTransaction == xa) {
+        associatedTransaction = null;
       }
     } finally {
       releaseMutex();
     }
   }
 
-  void abortWriteTransaction() throws MulgaraTransactionException {
-    acquireMutex();
+  public void closingSession() throws MulgaraTransactionException {
+    acquireMutexWithInterrupt(0, MulgaraTransactionException.class);
     try {
-      if (writeTransaction != null) {
-        writeTransaction.abortTransaction(new MulgaraTransactionException("Explicit abort requested by write-lock manager"));
-        writeTransaction = null;
+      try {
+        super.closingSession();
+      } finally {
+        transactions.clear();
       }
     } finally {
       releaseMutex();

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransaction.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransaction.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransaction.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -22,7 +22,6 @@
 // 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
@@ -82,7 +81,8 @@
   private Transaction transaction;
   private Thread currentThread;
 
-  private ReentrantLock activationMutex;
+  private volatile long deactivateTime;
+  private boolean inXACompletion;
 
   private State state;
   private int inuse;
@@ -103,68 +103,63 @@
       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;
+      deactivateTime = 0;
     } finally {
       report("Finished Creating Transaction");
     }
   }
 
 
-  void activate() throws MulgaraTransactionException {
-//    report("Activating Transaction");
+  private void activate() throws MulgaraTransactionException {
+    debugReport("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.");
-        }
+      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();
+
+      deactivateTime = -1;
+      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);
       }
 
       try {
@@ -183,51 +178,45 @@
 
 
   private void deactivate() throws MulgaraTransactionException {
-//    report("Deactivating transaction");
+    debugReport("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.");
-        }
+      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();
-            }
+
+      deactivateTime = System.currentTimeMillis();
+      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 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();
+          }
+          break;
+        case FAILED:
+          // Nothing to do here.
+          break;
       }
     } catch (MulgaraTransactionException em) {
       throw em;
@@ -243,84 +232,91 @@
   //       references a transaction object that won't be started/activated
   //       until it is first used.
   public void reference() throws MulgaraTransactionException {
+    report("Referencing Transaction");
+
+    acquireMutex(0, false, MulgaraTransactionException.class);
     try {
-      report("Referencing Transaction");
-
-      synchronized (this) {
+      try {
         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);
+        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");
       }
-    } catch (MulgaraTransactionException em) {
-      throw em;
-    } catch (Throwable th) {
-      report("Error referencing transaction");
-      throw implicitRollback(th);
     } finally {
-      report("Leaving Reference Transaction");
+      releaseMutex();
     }
   }
 
   public void dereference() throws MulgaraTransactionException {
     report("Dereferencing Transaction");
+
+    acquireMutex(0, false, MulgaraTransactionException.class);
     try {
-      synchronized (this) {
+      try {
         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 {
+        switch (state) {
+          case ACTREF:
+            if (using == 1) {
+              state = State.ACTUNREF;
+            }
             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");
+            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");
       }
-    } catch (MulgaraTransactionException em) {
-      throw em;
-    } catch (Throwable th) {
-      throw implicitRollback(th);
     } finally {
-      report("Dereferenced Transaction");
+      releaseMutex();
     }
   }
 
@@ -328,9 +324,7 @@
     report("Initiating transaction");
     try {
       transaction = factory.transactionStart(this);
-      synchronized (this) {
-        currentThread = Thread.currentThread();
-      }
+      currentThread = Thread.currentThread();
     } catch (Throwable th) {
       throw abortTransaction("Failed to start transaction", th);
     }
@@ -340,11 +334,9 @@
 //    report("Resuming transaction");
     try {
       factory.transactionResumed(this, transaction);
-      synchronized (this) {
-        currentThread = Thread.currentThread();
-      }
+      currentThread = Thread.currentThread();
     } catch (Throwable th) {
-      abortTransaction("Failed to resume transaction", th);
+      throw abortTransaction("Failed to resume transaction", th);
     }
   }
 
@@ -356,9 +348,7 @@
             new MulgaraTransactionException("Attempt to suspend unreferenced transaction"));
       }
       transaction = factory.transactionSuspended(this);
-      synchronized (this) {
-        currentThread = null;
-      }
+      currentThread = null;
       state = State.DEACTREF;
     } catch (Throwable th) {
       throw implicitRollback(th);
@@ -369,35 +359,77 @@
 
   public void commitTransaction() throws MulgaraTransactionException {
     report("Committing Transaction");
+    acquireMutex(0, true, MulgaraTransactionException.class);
     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);
+        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 {
+          report("Committed transaction");
+        } } } } }
+      } catch (Throwable th) {
+        errorReport("Error cleaning up transaction post-commit", th);
+        throw new MulgaraTransactionException("Error cleaning up transaction post-commit", th);
+      }
+    } finally {
+      releaseMutex();
     }
   }
 
   public void heuristicRollback(String cause) throws MulgaraTransactionException {
-    implicitRollback(new MulgaraTransactionException(cause));
+    synchronized (factory.getMutexLock()) {
+      if (factory.getMutexHolder() != null && factory.getMutexHolder() != Thread.currentThread()) {
+        if (inXACompletion) {
+          return;       // this txn is already being cleaned up, so let it go
+        }
+      }
+
+      factory.acquireMutexWithInterrupt(0L, MulgaraTransactionException.class);
+      inXACompletion = true;
+    }
+
+    try {
+      switch (state) {
+        case DEACTREF:
+          activate();
+          try {
+            implicitRollback(new MulgaraTransactionException(cause));
+          } finally {
+            currentThread = null;
+          }
+          break;
+
+        case ACTUNREF:
+        case ACTREF:
+          implicitRollback(new MulgaraTransactionException(cause));
+          break;
+
+        case CONSTRUCTEDREF:
+        case CONSTRUCTEDUNREF:
+          // no point in starting and then rolling back immediately, so just clean up
+          abortTransaction(cause, new Throwable());
+
+        case FINISHED:
+        case FAILED:
+          // Nothing to do here.
+          return;
+      }
+    } finally {
+      releaseMutex();
+    }
   }
 
   /**
@@ -406,39 +438,43 @@
    * after all we requested it.
    */
   public void explicitRollback() throws MulgaraTransactionException {
-    synchronized (this) {
+    acquireMutex(0, true, MulgaraTransactionException.class);
+    try {
       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");
+      try {
+        switch (state) {
+          case ACTUNREF:
+          case ACTREF:
+            transaction.rollback();
+            context.clear();
+            enlisted.clear();
+            factory.transactionComplete(this);
+            transaction = null;
+            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);
       }
-    } catch (MulgaraTransactionException em) {
-      throw em;
-    } catch (Throwable th) {
-      throw implicitRollback(th);
+    } finally {
+      releaseMutex();
     }
   }
 
@@ -452,18 +488,14 @@
    * Post-condition: The transaction is terminated and cleaned up.
    */
   @SuppressWarnings("finally")
-  MulgaraTransactionException implicitRollback(Throwable cause) throws MulgaraTransactionException {
-    try {
-      report("Implicit Rollback triggered");
+  private MulgaraTransactionException implicitRollback(Throwable cause) throws MulgaraTransactionException {
+    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.");
-        }
-      }
+    synchronized (factory.getMutexLock()) {
+      inXACompletion = true;
+    }
 
+    try {
       if (rollbackCause != null) {
         errorReport("Cascading error, transaction already rolled back", cause);
         errorReport("Cascade error, expected initial cause", rollbackCause);
@@ -480,8 +512,7 @@
             context.clear();
             enlisted.clear();
             state = State.FAILED;
-            factory.transactionComplete(this);
-            factory = null;
+            factory.transactionAborted(this);
             return new MulgaraTransactionException("Transaction rollback triggered", cause);
         case DEACTREF:
           throw new IllegalStateException("Attempt to rollback deactivated transaction");
@@ -529,36 +560,40 @@
    */
   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!
+    // we should actually already have the mutex, but let's make sure
+    acquireMutex(0L, true, MulgaraTransactionException.class);
     try {
+      // 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 {
-        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;
+        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 {
+          state = State.FAILED;
+        } } } } } } }
+        return new MulgaraTransactionException(errorMessage + " - Aborting", cause);
+      } catch (Throwable th) {
+        throw new MulgaraTransactionException(errorMessage + " - Failed to abort cleanly", th);
       } finally {
-        state = State.FAILED;
-      } } } } } } } }
-      return new MulgaraTransactionException(errorMessage + " - Aborting", cause);
-    } catch (Throwable th) {
-      throw new MulgaraTransactionException(errorMessage + " - Failed to abort cleanly", th);
+        report("Leaving abortTransaction");
+      }
     } finally {
-      report("Leaving abortTransaction");
+      releaseMutex();
     }
   }
 
@@ -579,105 +614,128 @@
 
   public void execute(Operation operation, DatabaseMetadata metadata) throws MulgaraTransactionException {
     report("Executing Operation");
+
+    acquireMutex(0, false, MulgaraTransactionException.class);
     try {
-      activate();
       try {
-        operation.execute(context,
-                          context.getSystemResolver(),
-                          metadata);
-      } catch (Throwable th) {
-        throw implicitRollback(th);
+        activate();
+        try {
+          operation.execute(context,
+                            context.getSystemResolver(),
+                            metadata);
+        } catch (Throwable th) {
+          throw implicitRollback(th);
+        } finally {
+          deactivate();
+        }
       } finally {
-        deactivate();
+        report("Executed Operation");
       }
     } finally {
-      report("Executed Operation");
+      releaseMutex();
     }
   }
 
   public AnswerOperationResult execute(AnswerOperation ao) throws TuplesException {
     debugReport("Executing AnswerOperation");
+
+    acquireMutex(0, false, TuplesException.class);
     try {
-      activate();
       try {
-        ao.execute();
-        return ao.getResult();
-      } catch (Throwable th) {
-        throw implicitRollback(th);
+        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 {
-        deactivate();
+        debugReport("Executed AnswerOperation");
       }
-    } catch (MulgaraTransactionException em) {
-      throw new TuplesException("Transaction error", em);
     } finally {
-      debugReport("Executed AnswerOperation");
+      releaseMutex();
     }
   }
 
 
   public void execute(TransactionOperation to) throws MulgaraTransactionException {
     report("Executing TransactionOperation");
+
+    acquireMutex(0, false, MulgaraTransactionException.class);
     try {
-      activate();
       try {
-        to.execute();
-      } catch (Throwable th) {
-        throw implicitRollback(th);
+        activate();
+        try {
+          to.execute();
+        } catch (Throwable th) {
+          throw implicitRollback(th);
+        } finally {
+          deactivate();
+        }
       } finally {
-        deactivate();
+        report("Executed TransactionOperation");
       }
     } finally {
-      report("Executed TransactionOperation");
+      releaseMutex();
     }
   }
 
   public void enlist(EnlistableResource enlistable) throws MulgaraTransactionException {
+    acquireMutex(0, false, MulgaraTransactionException.class);
     try {
-      synchronized (this) {
+      try {
         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;
-      }
+        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");
+        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);
       }
-    } catch (Throwable th) {
-      throw implicitRollback(th);
+    } finally {
+      releaseMutex();
     }
   }
 
+  public long lastActive() {
+    return deactivateTime;
+  }
+
   //
   // 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.");
-      }
+    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) {
@@ -700,12 +758,15 @@
     }
   }
 
-  private void acquireActivationMutex() {
-    activationMutex.lock();
+  private <T extends Throwable> void acquireMutex(long timeout, boolean isXACompletion, Class<T> exc) throws T {
+    synchronized (factory.getMutexLock()) {
+      factory.acquireMutex(timeout, exc);
+      inXACompletion = isXACompletion;
+    }
   }
 
-  private void releaseActivationMutex() {
-    activationMutex.unlock();
+  private void releaseMutex() {
+    factory.releaseMutex();
   }
 
   protected void finalize() {
@@ -723,7 +784,7 @@
       errorReport("Reference counting error in transaction", null);
     }
 
-    if (factory != null || transaction != null) {
+    if (transaction != null) {
       errorReport("Transaction not terminated properly", null);
     }
   }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransactionFactory.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransactionFactory.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransactionFactory.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -21,7 +21,6 @@
 package org.mulgara.resolver;
 
 // Java2 packages
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -36,7 +35,6 @@
 // Local packages
 import org.mulgara.query.MulgaraTransactionException;
 import org.mulgara.transaction.TransactionManagerFactory;
-import org.mulgara.util.Assoc1toNMap;
 import org.mulgara.util.StackTrace;
 
 /**
@@ -58,49 +56,60 @@
   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;
+  private boolean isFailed;
 
   /** Map of threads to active transactions. */
-  private Map<Thread, MulgaraTransaction> activeTransactions;
+  private final Map<Thread, MulgaraTransaction> activeTransactions;
 
-  private Assoc1toNMap<DatabaseSession, MulgaraTransaction> sessionXAMap;
+  /** Are we in auto-commit mode. */
+  public boolean autoCommit;
 
+  /** All uncompleted transactions (may be more than 1 because of unclosed answers) */
+  public final Set<MulgaraTransaction> transactions;
+
+  /** Currently associated explicit transaction */
+  public MulgaraInternalTransaction explicitXA;
+
   private final TransactionManager transactionManager;
 
-  public MulgaraInternalTransactionFactory(MulgaraTransactionManager manager, TransactionManagerFactory transactionManagerFactory) {
-    super(manager);
-    this.autoCommit = true;
+  public MulgaraInternalTransactionFactory(DatabaseSession session, MulgaraTransactionManager manager,
+                                           TransactionManagerFactory transactionManagerFactory) {
+    super(session, manager);
 
-    this.failedSessions = new HashSet<DatabaseSession>();
+    this.isFailed = false;
     this.activeTransactions = new HashMap<Thread, MulgaraTransaction>();
-    this.sessionXAMap = new Assoc1toNMap<DatabaseSession, MulgaraTransaction>();
+    this.autoCommit = true;
+    this.transactions = new HashSet<MulgaraTransaction>();
+    this.explicitXA = null;
 
     this.transactionManager = transactionManagerFactory.newTransactionManager();
+    try {
+      /* "disable" the TM's timeout because we implement timeouts ourselves; we also don't set
+       * it to our timeout because that can interfere with txn cleanup if the TM's timeout has
+       * fired by causing rollback or commit to fail.
+       */
+      this.transactionManager.setTransactionTimeout(Integer.MAX_VALUE);
+    } catch (SystemException es) {
+      logger.warn("Unable to disable transaction timeout on jta tm", es);
+    }
   }
 
-  public MulgaraTransaction getTransaction(final DatabaseSession session, boolean write)
-      throws MulgaraTransactionException {
-    acquireMutex();
+  public MulgaraTransaction getTransaction(boolean write) throws MulgaraTransactionException {
+    acquireMutex(0, MulgaraTransactionException.class);
     try {
-      if (manager.isHoldingWriteLock(session)) {
-        return writeTransaction;
+      if (explicitXA != null) {
+        return explicitXA;
       }
 
       try {
         MulgaraInternalTransaction transaction;
         if (write) {
-          runWithoutMutex(new TransactionOperation() {
-            public void execute() throws MulgaraTransactionException {
-              manager.obtainWriteLock(session);
-            }
-          });
+          manager.obtainWriteLock(session);
           try {
             assert writeTransaction == null;
             writeTransaction = transaction = 
-                new MulgaraInternalTransaction(this, session.newOperationContext(true));
+              new MulgaraInternalTransaction(this, session.newOperationContext(true));
           } catch (Throwable th) {
             manager.releaseWriteLock(session);
             throw new MulgaraTransactionException("Error creating write transaction", th);
@@ -109,7 +118,8 @@
           transaction = new MulgaraInternalTransaction(this, session.newOperationContext(false));
         }
 
-        sessionXAMap.put(session, transaction);
+        transactions.add(transaction);
+        transactionCreated(transaction);
 
         return transaction;
       } catch (MulgaraTransactionException em) {
@@ -122,36 +132,25 @@
     }
   }
 
-  public Set<MulgaraTransaction> getTransactionsForSession(DatabaseSession session) {
-    acquireMutex();
-    try {
-      Set <MulgaraTransaction> xas = sessionXAMap.getN(session);
-      return xas == null ? Collections.<MulgaraTransaction>emptySet() : xas;
-    } finally {
-      releaseMutex();
-    }
+  public Set<MulgaraTransaction> getTransactions() {
+    return transactions;
   }
 
-  public MulgaraTransaction newMulgaraTransaction(DatabaseOperationContext context)
-      throws MulgaraTransactionException {
-    return new MulgaraInternalTransaction(this, context);
-  }
 
-
-  public void commit(DatabaseSession session) throws MulgaraTransactionException {
-    acquireMutex();
+  public void commit() throws MulgaraTransactionException {
+    acquireMutex(0, MulgaraTransactionException.class);
     try {
+      if (isFailed) {
+        throw new MulgaraTransactionException("Attempting to commit failed session");
+      } else if (!manager.isHoldingWriteLock(session)) {
+        throw new MulgaraTransactionException(
+            "Attempting to commit while not the current writing transaction");
+      }
+
       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);
+        setAutoCommit(true);
+        setAutoCommit(false);
       } finally {
         manager.releaseReserve(session);
       }
@@ -166,17 +165,18 @@
    * 
    * This needs to be distinguished from an implicit rollback triggered by failure.
    */
-  public void rollback(DatabaseSession session) throws MulgaraTransactionException {
-    acquireMutex();
+  public void rollback() throws MulgaraTransactionException {
+    acquireMutex(0, MulgaraTransactionException.class);
     try {
-      manager.reserveWriteLock(session);
-      try {
-        if (manager.isHoldingWriteLock(session)) {
+      if (manager.isHoldingWriteLock(session)) {
+        manager.reserveWriteLock(session);
+        try {
           try {
             writeTransaction.execute(new TransactionOperation() {
-                public void execute() throws MulgaraTransactionException {
-                  writeTransaction.heuristicRollback("Explicit Rollback");
-                }
+              public void execute() throws MulgaraTransactionException {
+                writeTransaction.dereference();
+                ((MulgaraInternalTransaction)writeTransaction).explicitRollback();
+              }
             });
             // FIXME: Should be checking status here, not writelock.
             if (manager.isHoldingWriteLock(session)) {
@@ -185,47 +185,46 @@
                   new MulgaraTransactionException("Rollback failed to terminate write transaction"));
             }
           } finally {
-            failedSessions.add(session);
-            setAutoCommit(session, false);
+            explicitXA = null;
+            setAutoCommit(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 {
-        manager.releaseReserve(session);
+      } else if (isFailed) {
+        explicitXA = null;
+        isFailed = false;
+        setAutoCommit(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();
+  public void setAutoCommit(boolean autoCommit) throws MulgaraTransactionException {
+    acquireMutex(0, MulgaraTransactionException.class);
     try {
-      if (manager.isHoldingWriteLock(session) && failedSessions.contains(session)) {
+      if (manager.isHoldingWriteLock(session) && isFailed) {
         writeTransaction.abortTransaction("Session failed and still holding writeLock",
-            new MulgaraTransactionException("Failed Session in setAutoCommit"));
+                              new MulgaraTransactionException("Failed Session in setAutoCommit"));
       }
 
-      if (manager.isHoldingWriteLock(session) || failedSessions.contains(session)) {
+      if (manager.isHoldingWriteLock(session) || isFailed) {
         if (autoCommit) {
+          this.autoCommit = true;
+          this.explicitXA = null;
+
           // AutoCommit off -> on === branch on current state of transaction.
           if (manager.isHoldingWriteLock(session)) {
             // Within active transaction - commit and finalise.
             try {
-              runWithoutMutex(new TransactionOperation() {
+              writeTransaction.execute(new TransactionOperation() {
                 public void execute() throws MulgaraTransactionException {
-                  writeTransaction.execute(new TransactionOperation() {
-                    public void execute() throws MulgaraTransactionException {
-                      writeTransaction.dereference();
-                      ((MulgaraInternalTransaction)writeTransaction).commitTransaction();
-                    }
-                  });
+                  writeTransaction.dereference();
+                  ((MulgaraInternalTransaction)writeTransaction).commitTransaction();
                 }
               });
             } finally {
@@ -235,20 +234,14 @@
               if (manager.isHoldingWriteLock(session)) {
                 manager.releaseWriteLock(session);
               }
-              this.autoCommit = true;
             }
-          } else if (failedSessions.contains(session)) {
+          } else if (isFailed) {
             // Within failed transaction - cleanup.
-            failedSessions.remove(session);
+            isFailed = false;
           }
         } else {
           if (!manager.isHoldingWriteLock(session)) {
-            if (failedSessions.contains(session)) {
-              failedSessions.remove(session);
-              setAutoCommit(session, false);
-            } else {
-              throw new IllegalStateException("Can't reach here");
-            }
+            throw new MulgaraTransactionException("Attempting set auto-commit false in failed session");
           } else {
             // AutoCommit off -> off === no-op. Log info.
             if (logger.isInfoEnabled()) {
@@ -257,13 +250,15 @@
           }
         }
       } else {
+        explicitXA = null;
         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.
+          getTransaction(true); // Set's writeTransaction.
           writeTransaction.reference();
+          explicitXA = (MulgaraInternalTransaction) writeTransaction;
           this.autoCommit = false;
         }
       }
@@ -277,7 +272,7 @@
   //
 
   public Transaction transactionStart(MulgaraTransaction transaction) throws MulgaraTransactionException {
-    acquireMutex();
+    acquireMutex(0, MulgaraTransactionException.class);
     try {
       try {
         logger.info("Beginning Transaction");
@@ -304,7 +299,7 @@
 
   public void transactionResumed(MulgaraTransaction transaction, Transaction jtaXA) 
       throws MulgaraTransactionException {
-    acquireMutex();
+    acquireMutex(0, MulgaraTransactionException.class);
     try {
       if (activeTransactions.get(Thread.currentThread()) != null) {
         throw new MulgaraTransactionException(
@@ -312,7 +307,7 @@
       } else if (activeTransactions.containsValue(transaction)) {
         throw new MulgaraTransactionException("Attempt to resume active transaction");
       }
-      
+
       try {
         transactionManager.resume(jtaXA);
         activeTransactions.put(Thread.currentThread(), transaction);
@@ -326,7 +321,7 @@
 
   public Transaction transactionSuspended(MulgaraTransaction transaction)
       throws MulgaraTransactionException {
-    acquireMutex();
+    acquireMutex(0, MulgaraTransactionException.class);
     try {
       try {
         if (transaction != activeTransactions.get(Thread.currentThread())) {
@@ -358,22 +353,34 @@
     }
   }
 
+  public void closingSession() throws MulgaraTransactionException {
+    acquireMutexWithInterrupt(0, MulgaraTransactionException.class);
+    try {
+      try {
+        super.closingSession();
+      } finally {
+        transactions.clear();
+      }
+    } finally {
+      releaseMutex();
+    }
+  }
+
   public void transactionComplete(MulgaraTransaction transaction) throws MulgaraTransactionException {
-    acquireMutex();
+    acquireMutex(0, MulgaraTransactionException.class);
     try {
+      super.transactionComplete(transaction);
+
       logger.debug("Transaction Complete");
+
       if (transaction == writeTransaction) {
-        DatabaseSession session = sessionXAMap.get1(transaction);
-        if (session == null) {
-          throw new MulgaraTransactionException("No associated session found for write transaction");
-        }
         if (manager.isHoldingWriteLock(session)) {
           manager.releaseWriteLock(session);
           writeTransaction = null;
         }
       }
 
-      sessionXAMap.removeN(transaction);
+      transactions.remove(transaction);
       activeTransactions.remove(Thread.currentThread());
     } finally {
       releaseMutex();
@@ -381,12 +388,12 @@
   }
 
   public void transactionAborted(MulgaraTransaction transaction) {
-    acquireMutex();
+    acquireMutex(0, RuntimeException.class);
     try {
       try {
         // Make sure this cleans up the transaction metadata - this transaction is DEAD!
-        if (transaction == writeTransaction) {
-          failedSessions.add(sessionXAMap.get1(transaction));
+        if (!autoCommit && transaction == writeTransaction) {
+          isFailed = true;
         }
         transactionComplete(transaction);
       } catch (Throwable th) {
@@ -397,24 +404,4 @@
       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(new MulgaraTransactionException("Explicit abort requested by write-lock manager"));
-        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-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransaction.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -22,12 +22,10 @@
 // Java 2 enterprise packages
 
 // 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;
@@ -71,6 +69,13 @@
    */
   MulgaraTransactionException abortTransaction(String errorMessage, Throwable cause) throws MulgaraTransactionException;
 
+  /**
+   * Perform a heuristic rollback on the transaction.
+   *
+   * @param cause the reason for the rollback
+   * @throws MulgaraTransactionException if an error is encounted while attempting to roll back;
+   *                                     the transaction will still have been terminated, though.
+   */
   void heuristicRollback(String cause) throws MulgaraTransactionException;
 
   /**
@@ -96,4 +101,11 @@
    * abort().
    */
   public void enlist(EnlistableResource enlistable) throws MulgaraTransactionException;
+
+  /**
+   * Returns the currentTimeMillis when the transaction last used;
+   *   -1 if it is currently in use; or
+   *    0 if it has never been used.
+   */
+  public long lastActive();
 }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -21,24 +21,19 @@
 package org.mulgara.resolver;
 
 // Java2 packages
+import java.util.Date;
 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;
+import java.util.Timer;
+import java.util.TimerTask;
 
 // 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.
@@ -63,27 +58,55 @@
  */
 
 public abstract class MulgaraTransactionFactory {
-  private static final Logger logger =
-    Logger.getLogger(MulgaraTransactionFactory.class.getName());
+  private static final Logger logger = Logger.getLogger(MulgaraTransactionFactory.class.getName());
+  private static final Timer reaperTimer = new Timer("Write-lock Reaper", true);
 
   protected final MulgaraTransactionManager manager;
-  
+
+  protected final DatabaseSession session;
+
+  private final Map<MulgaraTransaction, XAReaper> timeoutTasks;
+
   /**
    * 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.
+   *
+   * Modifications of this must be holding the mutexLock.
    */
   protected MulgaraTransaction writeTransaction;
 
-  private ReentrantLock mutex;
+  private Thread mutexHolder;
+  private int lockCnt;
 
-  protected MulgaraTransactionFactory(MulgaraTransactionManager manager) {
+  protected MulgaraTransactionFactory(DatabaseSession session, MulgaraTransactionManager manager) {
+    this.session = session;
     this.manager = manager;
-    this.mutex = new ReentrantLock();
+    this.timeoutTasks = new HashMap<MulgaraTransaction, XAReaper>();
+    this.mutexHolder = null;
+    this.lockCnt = 0;
     this.writeTransaction = null;
   }
 
+  protected void transactionCreated(MulgaraTransaction transaction) {
+    long idleTimeout = session.getIdleTimeout() > 0 ? session.getIdleTimeout() : 15*60*1000L;
+    long txnTimeout = session.getTransactionTimeout() > 0 ? session.getTransactionTimeout() : 60*60*1000L;
+    long now = System.currentTimeMillis();
 
+    synchronized (getMutexLock()) {
+      timeoutTasks.put(transaction, new XAReaper(transaction, now + txnTimeout, idleTimeout, now));
+    }
+  }
+
+  protected void transactionComplete(MulgaraTransaction transaction) throws MulgaraTransactionException {
+    synchronized (getMutexLock()) {
+      XAReaper reaper = timeoutTasks.remove(transaction);
+      if (reaper != null) {
+        reaper.cancel();
+      }
+    }
+  }
+
   /**
    * Obtain a transaction context associated with a DatabaseSession.
    *
@@ -94,20 +117,21 @@
    * otherwise creates a new transaction context and associates it with the
    * session.
    */
-  public abstract MulgaraTransaction getTransaction(final DatabaseSession session, boolean write)
+  public abstract MulgaraTransaction getTransaction(boolean write)
       throws MulgaraTransactionException;
-  
-  protected abstract Set<? extends MulgaraTransaction> getTransactionsForSession(DatabaseSession session);
 
+  protected abstract Set<? extends MulgaraTransaction> getTransactions();
+
   /**
    * 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");
+  public void closingSession() throws MulgaraTransactionException {
+    acquireMutex(0, MulgaraTransactionException.class);
     try {
+      logger.debug("Cleaning up any stale transactions on session close");
+
       Map<MulgaraTransaction, Throwable> requiresAbort = new HashMap<MulgaraTransaction, Throwable>();
       try {
         Throwable error = null;
@@ -140,7 +164,7 @@
           logger.debug("Session does not hold write-lock");
         }
 
-        for (MulgaraTransaction transaction : getTransactionsForSession(session)) {
+        for (MulgaraTransaction transaction : new HashSet<MulgaraTransaction>(getTransactions())) {
           try {
             // This is final so we can create the closure.
             final MulgaraTransaction xa = transaction;
@@ -168,12 +192,20 @@
             logger.error("Error aborting transactions after heuristic rollback failure on session close", th);
           } catch (Throwable throw_away) { }
         }
+
+        synchronized (getMutexLock()) {
+          for (XAReaper reaper : timeoutTasks.values()) {
+            reaper.cancel();
+          }
+          timeoutTasks.clear();
+        }
       }
     } finally {
       releaseMutex();
     }
   }
 
+
   /**
    * Abort as many of the transactions as we can.
    */
@@ -211,40 +243,169 @@
     }
   }
 
+  /** 
+   * Acquire the mutex. The mutex is re-entrant, but {@link #releaseMutex} must be called as many
+   * times as this is called.
+   *
+   * <p>We use our own implementation here (as opposed to, say, java.util.concurrent.Lock) so we
+   * can reliably get the current mutex-owner, and we use a lock around the mutex acquisition and
+   * release to do atomic tests and settting of additional variables associated with the mutex.
+   * 
+   * @param timeout how many milliseconds to wait for the mutex, or 0 to wait indefinitely
+   * @param T       the type of exception to throw on failure
+   * @throws T if the mutex could not be acquired, either due to a timeout or due to an interrupt
+   */
+  public final <T extends Throwable> void acquireMutex(long timeout, Class<T> exc) throws T {
+    synchronized (getMutexLock()) {
+      if (mutexHolder == Thread.currentThread()) {
+        lockCnt++;
+        return;
+      }
+
+      long deadline = System.currentTimeMillis() + timeout;
+
+      while (mutexHolder != null) {
+        long wait = deadline - System.currentTimeMillis();
+        if (timeout == 0) {
+          wait = 0;
+        } else if (wait <= 0) {
+          throw newException(exc, "Timed out waiting to acquire lock");
+        }
+
+        try {
+          getMutexLock().wait(wait);
+        } catch (InterruptedException ie) {
+          throw newExceptionOrCause(exc, "Interrupted while waiting to acquire lock", ie);
+        }
+      }
+
+      mutexHolder = Thread.currentThread();
+      lockCnt = 1;
+    }
+  }
+
+  /** 
+   * Acquire the mutex, interrupting the existing holder if there is one. 
+   * 
+   * @param timeout how many milliseconds to wait for the mutex, or 0 to wait indefinitely
+   * @param T       the type of exception to throw on failure
+   * @throws T if the mutex could not be acquired, either due to a timeout or due to an interrupt
+   * @see #acquireMutex
+   */
+  public final <T extends Throwable> void acquireMutexWithInterrupt(long timeout, Class<T> exc) throws T {
+    synchronized (getMutexLock()) {
+      if (mutexHolder != null && mutexHolder != Thread.currentThread()) {
+        mutexHolder.interrupt();
+      }
+
+      acquireMutex(timeout, exc);
+    }
+  }
+
+  /** 
+   * Release the mutex. 
+   *
+   * @throws IllegalStateException is the mutex is not held by the current thread
+   */
+  public final void releaseMutex() {
+    synchronized (getMutexLock()) {
+      if (Thread.currentThread() != mutexHolder) {
+        throw new IllegalStateException("Attempt to release mutex without holding mutex");
+      }
+
+      assert lockCnt > 0;
+      if (--lockCnt <= 0) {
+        mutexHolder = null;
+        getMutexLock().notify();
+      }
+    }
+  }
+
   /**
-   * 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.
+   * @return the lock used to protect access to and to implement the mutex; must be held as shortly
+   *         as possible (no blocking operations)
    */
-  protected void acquireMutex() {
-    mutex.lock();
+  public final Object getMutexLock() {
+    return session;
   }
 
+  /**
+   * @return the current holder of the mutex, or null if none. Must hold the mutex-lock while
+   *         calling this.
+   */
+  public final Thread getMutexHolder() {
+    return mutexHolder;
+  }
 
-  protected void releaseMutex() {
-    if (!mutex.isHeldByCurrentThread()) {
-      throw new IllegalStateException("Attempt to release mutex without holding mutex");
+  public static <T extends Throwable> T newException(Class<T> exc, String msg) {
+    try {
+      return exc.getConstructor(String.class).newInstance(msg);
+    } catch (Exception e) {
+      throw new Error("Internal error creating " + exc, e);
     }
+  }
 
-    mutex.unlock();
+  public static <T extends Throwable> T newExceptionOrCause(Class<T> exc, String msg, Throwable cause) {
+    if (exc.isAssignableFrom(cause.getClass()))
+      return exc.cast(cause);
+    return exc.cast(newException(exc, msg).initCause(cause));
   }
 
-  protected void runWithoutMutex(TransactionOperation proc) throws MulgaraTransactionException {
-    if (!mutex.isHeldByCurrentThread()) {
-      throw new IllegalStateException("Attempt to run procedure without holding mutex");
+  private class XAReaper extends TimerTask {
+    private final MulgaraTransaction transaction;
+    private final long txnDeadline;
+    private final long idleTimeout;
+
+    public XAReaper(MulgaraTransaction transaction, long txnDeadline, long idleTimeout, long lastActive) {
+      this.transaction = transaction;
+      this.txnDeadline = txnDeadline;
+      this.idleTimeout = idleTimeout;
+
+      if (lastActive <= 0)
+        lastActive = System.currentTimeMillis();
+      long nextWakeup = Math.min(txnDeadline, lastActive + idleTimeout);
+
+      if (logger.isDebugEnabled()) {
+        logger.debug("Transaction-reaper created, txn=" + transaction + ", txnDeadline=" + txnDeadline +
+                     ", idleTimeout=" + idleTimeout + ", nextWakeup=" + nextWakeup + ": " +
+                     System.identityHashCode(getMutexLock()));
+      }
+
+      reaperTimer.schedule(this, new Date(nextWakeup));
     }
-    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();
+
+    public void run() {
+      logger.debug("Transaction-reaper running, txn=" + transaction + ": " + System.identityHashCode(getMutexLock()));
+
+      long lastActive = transaction.lastActive();
+      long now = System.currentTimeMillis();
+
+      synchronized (getMutexLock()) {
+        if (timeoutTasks.remove(transaction) == null)
+          return;       // looks like we got cleaned up
+
+        if (now < txnDeadline && ((lastActive <= 0) || (now < lastActive + idleTimeout))) {
+          if (logger.isDebugEnabled())
+            logger.debug("Transaction still active: " + lastActive + " time: " + now + " idle-timeout: " + idleTimeout + " - rescheduling timer");
+
+          timeoutTasks.put(transaction, new XAReaper(transaction, txnDeadline, idleTimeout, lastActive));
+          return;
+        }
       }
+
+      final String txnType = (now >= txnDeadline) ? "over-extended" : "inactive";
+      final String toType = (now >= txnDeadline) ? "transaction" : "idle";
+
+      logger.warn("Rolling back " + txnType + " transaction");
+      new Thread(toType + "-timeout executor") {
+        public void run() {
+          try {
+            transaction.heuristicRollback(toType + "-timeout");
+          } catch (MulgaraTransactionException em) {
+            logger.warn("Exception thrown while rolling back " + txnType + " transaction");
+          }
+        }
+      }.start();
     }
   }
 }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -30,18 +30,15 @@
 
 // Local packages
 import org.mulgara.query.MulgaraTransactionException;
-import org.mulgara.transaction.TransactionManagerFactory;
 import org.mulgara.util.StackTrace;
 
 /**
  * Manages the Write-Lock.
  *
  * Manages tracking the ownership of the write-lock.
- * 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.
+ * Maintains the write-queue
  *
  * @created 2006-10-06
  *
@@ -75,29 +72,14 @@
   private final ReentrantLock mutex;
   private final Condition writeLockCondition;
 
-  private final MulgaraInternalTransactionFactory internalFactory;
-  private final MulgaraExternalTransactionFactory externalFactory;
-
-  public MulgaraTransactionManager(TransactionManagerFactory transactionManagerFactory) {
+  public MulgaraTransactionManager() {
     this.sessionHoldingWriteLock = null;
     this.sessionReservingWriteLock = null;
     this.mutex = new ReentrantLock();
     this.writeLockCondition = this.mutex.newCondition();
-
-    this.internalFactory = new MulgaraInternalTransactionFactory(this, transactionManagerFactory);
-    this.externalFactory = new MulgaraExternalTransactionFactory(this);
   }
 
 
-  MulgaraInternalTransactionFactory getInternalFactory() {
-    return internalFactory;
-  }
-
-  MulgaraExternalTransactionFactory getExternalFactory() {
-    return externalFactory;
-  }
-
-
   /** 
    * Obtains the write lock.
    */
@@ -155,10 +137,6 @@
   }
 
 
-  public void setTransactionTimeout(int transactionTimeout) {
-    internalFactory.setTransactionTimeout(transactionTimeout);
-  }
-
   /**
    * Used to replace the built in monitor to allow it to be properly released
    * during potentially blocking operations.  All potentially blocking
@@ -192,11 +170,21 @@
   }
 
   boolean writeLockReserved() {
-    return sessionReservingWriteLock != null;
+    acquireMutex();
+    try {
+      return sessionReservingWriteLock != null;
+    } finally {
+      releaseMutex();
+    }
   }
 
   boolean writeLockReserved(DatabaseSession session) {
-    return session == sessionReservingWriteLock;
+    acquireMutex();
+    try {
+      return session == sessionReservingWriteLock;
+    } finally {
+      releaseMutex();
+    }
   }
 
   void releaseReserve(DatabaseSession session) {
@@ -229,28 +217,12 @@
   }
 
   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;
-    }
-
-    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 {
+      Throwable error = null;
+
       if (writeLockReserved(session)) {
         try {
           releaseReserve(session);

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -24,7 +24,6 @@
 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;
@@ -79,8 +78,6 @@
 
   private final Assoc1toNMap<MulgaraExternalTransaction, Xid> xa2xid;
 
-  private final ReentrantLock mutex;
-
   private final UUID uniqueId;
 
   MulgaraXAResourceContext(MulgaraExternalTransactionFactory factory, DatabaseSession session) {
@@ -88,7 +85,6 @@
     this.factory = factory;
     this.session = session;
     this.xa2xid = new Assoc1toNMap<MulgaraExternalTransaction, Xid>();
-    this.mutex = new ReentrantLock();
     this.uniqueId = UUID.randomUUID();
   }
 
@@ -114,7 +110,7 @@
      * for a call to forget().
      */
     public void commit(Xid xid, boolean onePhase) throws XAException {
-      acquireMutex();
+      factory.acquireMutex(0, XAException.class);
       try {
         xid = convertXid(xid);
         logger.info("Performing commit: " + parseXid(xid));
@@ -167,7 +163,7 @@
           }
         }
       } finally {
-        releaseMutex();
+        factory.releaseMutex();
       }
     }
 
@@ -181,7 +177,7 @@
      * In all cases disassociate from current session.
      */
     public void end(Xid xid, int flags) throws XAException {
-      acquireMutex();
+      factory.acquireMutex(0, XAException.class);
       try {
         xid = convertXid(xid);
         logger.info("Performing end(" + formatFlags(flags) + "): " + parseXid(xid));
@@ -208,18 +204,18 @@
 
         try {
           // If XA is currently associated with session, disassociate it.
-          factory.disassociateTransaction(session, xa);
+          factory.disassociateTransaction(xa);
         } catch (MulgaraTransactionException em) {
           logger.error("Error disassociating transaction from session", em);
           throw new XAException(XAException.XAER_PROTO);
         }
       } finally {
-        releaseMutex();
+        factory.releaseMutex();
       }
     }
 
     public void forget(Xid xid) throws XAException {
-      acquireMutex();
+      factory.acquireMutex(0, XAException.class);
       try {
         xid = convertXid(xid);
         logger.info("Performing forget: " + parseXid(xid));
@@ -240,22 +236,22 @@
           xa2xid.remove1(xa);
         }
       } finally {
-        releaseMutex();
+        factory.releaseMutex();
       }
     }
 
-    public int getTransactionTimeout() {
-      acquireMutex();
+    public int getTransactionTimeout() throws XAException {
+      factory.acquireMutex(0, XAException.class);
       try {
         logger.info("Performing getTransactionTimeout");
-        return 3600;
+        return (int) (session.getTransactionTimeout() / 1000);
       } finally {
-        releaseMutex();
+        factory.releaseMutex();
       }
     }
 
-    public boolean isSameRM(XAResource xares) {
-      acquireMutex();
+    public boolean isSameRM(XAResource xares) throws XAException {
+      factory.acquireMutex(0, XAException.class);
       try {
         logger.info("Performing isSameRM");
         if (!xares.getClass().equals(MulgaraXAResource.class)) {
@@ -267,13 +263,13 @@
           return session == ((MulgaraXAResource)xares).getSession();
         }
       } finally {
-        releaseMutex();
+        factory.releaseMutex();
       }
     }
 
 
     public int prepare(Xid xid) throws XAException {
-      acquireMutex();
+      factory.acquireMutex(0, XAException.class);
       try {
         xid = convertXid(xid);
         logger.info("Performing prepare: " + parseXid(xid));
@@ -288,7 +284,7 @@
 
         return XA_OK;
       } finally {
-        releaseMutex();
+        factory.releaseMutex();
       }
     }
 
@@ -297,19 +293,19 @@
      * 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();
+    public Xid[] recover(int flag) throws XAException {
+      factory.acquireMutex(0, XAException.class);
       try {
         logger.info("Performing recover");
         return new Xid[] {};
       } finally {
-        releaseMutex();
+        factory.releaseMutex();
       }
     }
 
 
     public void rollback(Xid xid) throws XAException {
-      acquireMutex();
+      factory.acquireMutex(0, XAException.class);
       try {
         xid = convertXid(xid);
         logger.info("Performing rollback: " + parseXid(xid));
@@ -323,7 +319,7 @@
         // transaction.  doRollback only throws Heuristic Exceptions.
         xa2xid.remove1(xa);
       } finally {
-        releaseMutex();
+        factory.releaseMutex();
       }
     }
 
@@ -342,19 +338,23 @@
     }
 
 
-    public boolean setTransactionTimeout(int seconds) {
-      acquireMutex();
+    public boolean setTransactionTimeout(int seconds) throws XAException {
+      if (seconds < 0)
+        throw new XAException(XAException.XAER_INVAL);
+
+      factory.acquireMutex(0, XAException.class);
       try {
         logger.info("Performing setTransactionTimeout");
-        return false;
+        session.setTransactionTimeout(seconds * 1000L);
+        return true;
       } finally {
-        releaseMutex();
+        factory.releaseMutex();
       }
     }
 
 
     public void start(Xid xid, int flags) throws XAException {
-      acquireMutex();
+      factory.acquireMutex(0, XAException.class);
       try {
         xid = convertXid(xid);
         logger.info("Performing start(" + formatFlags(flags) + "): " + parseXid(xid));
@@ -362,12 +362,12 @@
           case TMNOFLAGS:
             if (xa2xid.containsN(xid)) {
               throw new XAException(XAException.XAER_DUPID);
-            } else if (factory.hasAssociatedTransaction(session)) {
+            } else if (factory.hasAssociatedTransaction()) {
               throw new XAException(XAException.XA_RBDEADLOCK);
             } else {
               // FIXME: Need to consider read-only transactions here.
               try {
-                MulgaraExternalTransaction xa = factory.createTransaction(session, xid, writing);
+                MulgaraExternalTransaction xa = factory.createTransaction(xid, writing);
                 xa2xid.put(xa, xid);
               } catch (MulgaraTransactionException em) {
                 logger.error("Failed to create transaction", em);
@@ -376,9 +376,9 @@
             }
             break;
           case TMJOIN:
-            if (!factory.hasAssociatedTransaction(session)) {
+            if (!factory.hasAssociatedTransaction()) {
               throw new XAException(XAException.XAER_NOTA);
-            } else if (!factory.getAssociatedTransaction(session).getXid().equals(xid)) {
+            } else if (!factory.getAssociatedTransaction().getXid().equals(xid)) {
               throw new XAException(XAException.XAER_OUTSIDE);
             }
             break;
@@ -389,7 +389,7 @@
             } else if (xa.isRollbacked()) {
               throw new XAException(XAException.XA_RBROLLBACK);
             } else {
-              if (!factory.associateTransaction(session, xa)) {
+              if (!factory.associateTransaction(xa)) {
                 // session already associated with a transaction.
                 throw new XAException(XAException.XAER_PROTO);
               }
@@ -397,7 +397,7 @@
             break;
         }
       } finally {
-        releaseMutex();
+        factory.releaseMutex();
       }
 
     }
@@ -412,27 +412,7 @@
      */
     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();
   }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MutableLocalQueryImpl.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MutableLocalQueryImpl.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MutableLocalQueryImpl.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -36,7 +36,6 @@
 // Java 2 standard packages
 
 // Third party packages
-import org.apache.log4j.Logger;
 
 // Local packages
 import org.mulgara.query.*;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/Operation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/Operation.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/Operation.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -29,9 +29,7 @@
 
 // Local packages
 import org.mulgara.resolver.spi.DatabaseMetadata;
-import org.mulgara.resolver.spi.ResolverSessionFactory;
 import org.mulgara.resolver.spi.SystemResolver;
-import org.mulgara.server.Session;
 
 /**
  * {@link Operation}s correspond to methods of the {@link Session} interface

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/PreallocateOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/PreallocateOperation.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/PreallocateOperation.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -29,12 +29,7 @@
 
 // Java 2 standard packages
 import java.io.*;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.*;
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
 
 // Third party packages
 import org.apache.log4j.Logger;
@@ -42,7 +37,6 @@
 
 // Local packages
 import org.mulgara.resolver.spi.DatabaseMetadata;
-import org.mulgara.resolver.spi.ResolverSessionFactory;
 import org.mulgara.resolver.spi.SingletonStatements;
 import org.mulgara.resolver.spi.SystemResolver;
 

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/QueryOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/QueryOperation.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/QueryOperation.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -37,7 +37,6 @@
 // Local packages
 import org.mulgara.query.*;
 import org.mulgara.resolver.spi.*;
-import org.mulgara.server.Session;
 
 /**
  * An {@link Operation} that implements the {@link Session#query} method.

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/RemoveModelOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/RemoveModelOperation.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/RemoveModelOperation.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -29,34 +29,17 @@
 
 // Java 2 standard packages
 import java.io.*;
-import java.net.MalformedURLException;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.*;
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
 
-// Java 2 enterprise packages
-import javax.transaction.RollbackException;
-import javax.transaction.Status;
-import javax.transaction.SystemException;
-import javax.transaction.Transaction;
-import javax.transaction.TransactionManager;
-import javax.transaction.InvalidTransactionException;
-
 // Third party packages
 import org.apache.log4j.Logger;
 import org.jrdf.graph.*;
 
 // Local packages
-import org.mulgara.content.Content;
-import org.mulgara.content.ContentHandler;
-import org.mulgara.content.ContentHandlerManager;
-import org.mulgara.content.ContentLoader;
 import org.mulgara.query.*;
 import org.mulgara.query.rdf.*;
 import org.mulgara.resolver.spi.*;
-import org.mulgara.server.Session;
 import org.mulgara.store.nodepool.NodePool;
 
 /**

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/SetModelOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/SetModelOperation.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/SetModelOperation.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -29,21 +29,9 @@
 
 // Java 2 standard packages
 import java.io.*;
-import java.net.MalformedURLException;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.*;
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
 
-// Java 2 enterprise packages
-import javax.transaction.RollbackException;
-import javax.transaction.Status;
-import javax.transaction.SystemException;
-import javax.transaction.Transaction;
-import javax.transaction.TransactionManager;
-import javax.transaction.InvalidTransactionException;
-
 // Third party packages
 import org.apache.log4j.Logger;
 import org.jrdf.graph.*;
@@ -58,7 +46,6 @@
 import org.mulgara.query.*;
 import org.mulgara.query.rdf.*;
 import org.mulgara.resolver.spi.*;
-import org.mulgara.server.Session;
 
 /**
  * An {@link Operation} that implements the {@link Session#setModel} method.

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/StatusFormat.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/StatusFormat.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/StatusFormat.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -29,7 +29,6 @@
 
 // Java 2 enterprise packages
 import javax.transaction.Status;
-import javax.transaction.SystemException;
 
 /**
  * Generate a presentation form for a transaction {@link Status}.

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/StreamContent.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/StreamContent.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/StreamContent.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -28,7 +28,6 @@
 package org.mulgara.resolver;
 
 // Java 2 standard packages
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/StringPoolSession.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/StringPoolSession.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/StringPoolSession.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -123,6 +123,8 @@
 
   private Object globalLock;
 
+  private Thread currentThread;
+
   StringPoolSession(URI          databaseURI,
                     Set<String>  hostnameAliases,
                     XAStringPool persistentStringPool,
@@ -146,6 +148,7 @@
     this.temporaryNodePool = temporaryNodePool;
     this.globalLock = globalLock;
     this.state = OBTAIN;
+    this.currentThread = null;
   }
 
 
@@ -154,9 +157,10 @@
   //
 
   public Node globalize(long localNode) throws GlobalizeException {
-
     // this should not require guarding, as read-only operations will usually not be on the current phase
     // any reads on the current phase are about to start failing anyway if the state changes under us
+    // this should not require guarding, as read-only operations will usually not be on the current phase
+    // any reads on the current phase are about to start failing anyway if the state changes under us
     if (state == ROLLBACK || state == RELEASE) {
       throw new GlobalizeException(localNode, "Attempting to globalize outside transaction.");
     }
@@ -200,117 +204,152 @@
   }
 
   public long localizePersistent(Node node) throws LocalizeException {
-    return localize(node, WRITE | PERSIST);
+    checkCurrentThread();
+    try {
+      return localize(node, WRITE | PERSIST);
+    } finally {
+      releaseCurrentThread();
+    }
   }
 
   public long newBlankNode() throws NodePoolException {
-    return persistentNodePool.newNode();
+    checkCurrentThread();
+    try {
+      return persistentNodePool.newNode();
+    } finally {
+      releaseCurrentThread();
+    }
   }
 
   public void refresh(SimpleXAResource[] resources) throws SimpleXAResourceException {
-    if (logger.isDebugEnabled()) {
-      logger.debug("Obtaining phase on StringPoolSession " + System.identityHashCode(this));
-    }
-    this.resources = resources;
+    checkCurrentThread();
+    try {
+      if (logger.isDebugEnabled()) {
+        logger.debug("Obtaining phase on StringPoolSession " + System.identityHashCode(this));
+      }
+      this.resources = resources;
 
-    synchronized (this.globalLock) {
-      this.persistentStringPool.refresh();
-      this.persistentNodePool.refresh();
-      // !!Review: Call rollback on temporary? NB. Can't rollback non XA-SP/NP.
-      //this.temporaryStringPool.refresh();
-      //this.temporaryNodePool.refresh();
+      synchronized (this.globalLock) {
+        this.persistentStringPool.refresh();
+        this.persistentNodePool.refresh();
+        // !!Review: Call rollback on temporary? NB. Can't rollback non XA-SP/NP.
+        //this.temporaryStringPool.refresh();
+        //this.temporaryNodePool.refresh();
 
-      for (int i = 0; i < this.resources.length; i++) {
-        this.resources[i].refresh();
+        for (int i = 0; i < this.resources.length; i++) {
+          this.resources[i].refresh();
+        }
       }
+    } finally {
+      releaseCurrentThread();
     }
   }
 
 
   public void prepare() throws SimpleXAResourceException {
-    synchronized (globalLock) {
-      if (logger.isDebugEnabled()) {
-        logger.debug("Preparing phase on StringPoolSession " + System.identityHashCode(this) + " SP=" + System.identityHashCode(persistentStringPool));
+    checkCurrentThread();
+    try {
+      synchronized (globalLock) {
+        if (logger.isDebugEnabled()) {
+          logger.debug("Preparing phase on StringPoolSession " + System.identityHashCode(this) + " SP=" + System.identityHashCode(persistentStringPool));
+        }
+        if (state == PREPARE) {
+          return;
+        } else if (state != OBTAIN) {
+          throw new SimpleXAResourceException("Attempting to prepare phase without obtaining phase");
+        }
+    
+        state = PREPARE;
+    
+        persistentStringPool.prepare();
+        persistentNodePool.prepare();
+        for (int i = 0; i < resources.length; i++) {
+          resources[i].prepare();
+        }
       }
-      if (state == PREPARE) {
-        return;
-      } else if (state != OBTAIN) {
-        throw new SimpleXAResourceException("Attempting to prepare phase without obtaining phase");
-      }
-  
-      state = PREPARE;
-  
-      persistentStringPool.prepare();
-      persistentNodePool.prepare();
-      for (int i = 0; i < resources.length; i++) {
-        resources[i].prepare();
-      }
+    } finally {
+      releaseCurrentThread();
     }
   }
 
 
   public void commit() throws SimpleXAResourceException {
-    synchronized (globalLock) {
-      if (logger.isDebugEnabled()) {
-        logger.debug("Committing phase on StringPoolSession " + System.identityHashCode(this));
-      }
-      if (state == COMMIT) {
-        return;
-      } else if (state != PREPARE) {
-        throw new SimpleXAResourceException("Attempting to commit phase without preparing");
-      }
-  
-      state = COMMIT;
+    checkCurrentThread();
+    try {
+      synchronized (globalLock) {
+        if (logger.isDebugEnabled()) {
+          logger.debug("Committing phase on StringPoolSession " + System.identityHashCode(this));
+        }
+        if (state == COMMIT) {
+          return;
+        } else if (state != PREPARE) {
+          throw new SimpleXAResourceException("Attempting to commit phase without preparing");
+        }
+    
+        state = COMMIT;
 
-      persistentStringPool.commit();
-      persistentNodePool.commit();
-      for (int i = 0; i < resources.length; i++) {
-        resources[i].commit();
+        persistentStringPool.commit();
+        persistentNodePool.commit();
+        for (int i = 0; i < resources.length; i++) {
+          resources[i].commit();
+        }
       }
+    } finally {
+      releaseCurrentThread();
     }
   }
 
 
   public void rollback() throws SimpleXAResourceException {
-    synchronized (globalLock) {
-      if (logger.isDebugEnabled()) {
-        logger.debug("Rollback phase on StringPoolSession " + System.identityHashCode(this));
+    checkCurrentThread();
+    try {
+      synchronized (globalLock) {
+        if (logger.isDebugEnabled()) {
+          logger.debug("Rollback phase on StringPoolSession " + System.identityHashCode(this));
+        }
+        if (state == RELEASE) {
+          throw new SimpleXAResourceException("Attempting to rollback phase outside transaction");
+        }
+        state = ROLLBACK;
+        persistentStringPool.rollback();
+        persistentNodePool.rollback();
+        for (int i = 0; i < resources.length; i++) {
+          resources[i].rollback();
+        }
       }
-      if (state == RELEASE) {
-        throw new SimpleXAResourceException("Attempting to rollback phase outside transaction");
-      }
-      state = ROLLBACK;
-      persistentStringPool.rollback();
-      persistentNodePool.rollback();
-      for (int i = 0; i < resources.length; i++) {
-        resources[i].rollback();
-      }
+    } finally {
+      releaseCurrentThread();
     }
   }
 
 
   public void release() throws SimpleXAResourceException {
-    synchronized (globalLock) {
-      if (logger.isDebugEnabled()) {
-        logger.debug("Release phase on StringPoolSession " + System.identityHashCode(this));
+    checkCurrentThread();
+    try {
+      synchronized (globalLock) {
+        if (logger.isDebugEnabled()) {
+          logger.debug("Release phase on StringPoolSession " + System.identityHashCode(this));
+        }
+        if (state == RELEASE) {
+          return;
+        } else if (state != COMMIT && state != ROLLBACK) {
+          throw new SimpleXAResourceException("Attempting to release phase without commit or rollback");
+        }
+    
+        state = RELEASE;
+    
+        persistentStringPool.release();
+        persistentNodePool.release();
+    
+        // TODO determine if release() should be called for the temp components.
+        //temporaryStringPool.release();
+        //temporaryNodePool.release();
+        for (int i = 0; i < resources.length; i++) {
+          resources[i].release();
+        }
       }
-      if (state == RELEASE) {
-        return;
-      } else if (state != COMMIT && state != ROLLBACK) {
-        throw new SimpleXAResourceException("Attempting to release phase without commit or rollback");
-      }
-  
-      state = RELEASE;
-  
-      persistentStringPool.release();
-      persistentNodePool.release();
-  
-      // TODO determine if release() should be called for the temp components.
-      //temporaryStringPool.release();
-      //temporaryNodePool.release();
-      for (int i = 0; i < resources.length; i++) {
-        resources[i].release();
-      }
+    } finally {
+      releaseCurrentThread();
     }
   }
 
@@ -734,4 +773,25 @@
   public long findGNode(SPObject spObject) throws StringPoolException {
     return persistentStringPool.findGNode(spObject, persistentNodePool);
   }
+
+  /**
+   * Used purely as a sanity check in the hope that we might catch concurrency bugs in higher layers should
+   * they exist.
+   */
+  private void checkCurrentThread() {
+    synchronized(this) {
+      if (currentThread == null || currentThread.equals(Thread.currentThread())) {
+        currentThread = Thread.currentThread();
+      } else {
+        logger.warn("Concurrent Access of StringPoolSession Attempted");
+        throw new IllegalStateException("Concurrent Access of StringPoolSession Attempted");
+      }
+    }
+  }
+
+  private void releaseCurrentThread() {
+    synchronized(this) {
+      currentThread = null;
+    }
+  }
 }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/StringPoolSessionFactory.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/StringPoolSessionFactory.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/StringPoolSessionFactory.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -32,13 +32,7 @@
 import java.net.URI;
 import java.util.*;
 import java.lang.reflect.Method;
-import javax.transaction.xa.XAResource;
 
-// Java 2 enterprise packages
-import javax.transaction.RollbackException;
-import javax.transaction.Transaction;
-import javax.transaction.TransactionManager;
-
 // Third party packages
 import org.apache.log4j.Logger;
 import org.jrdf.graph.*;
@@ -48,22 +42,13 @@
 import org.mulgara.query.*;
 import org.mulgara.query.rdf.*;
 import org.mulgara.resolver.spi.*;
-import org.mulgara.server.Session;
-import org.mulgara.server.SessionFactory;
-import org.mulgara.store.nodepool.NodePool;
 import org.mulgara.store.nodepool.NodePoolException;
 import org.mulgara.store.nodepool.NodePoolFactory;
-import org.mulgara.store.statement.StatementStore;
-import org.mulgara.store.stringpool.StringPool;
 import org.mulgara.store.stringpool.StringPoolException;
 import org.mulgara.store.stringpool.StringPoolFactory;
-import org.mulgara.store.tuples.Tuples;
-import org.mulgara.store.tuples.TuplesOperations;
 import org.mulgara.store.xa.SimpleXARecoveryHandler;
-import org.mulgara.store.xa.SimpleXAResource;
 import org.mulgara.store.xa.SimpleXAResourceException;
 import org.mulgara.store.xa.XANodePool;
-import org.mulgara.store.xa.XAResolverSession;
 import org.mulgara.store.xa.XAResolverSessionFactory;
 import org.mulgara.store.xa.XAStatementStore;
 import org.mulgara.store.xa.XAStringPool;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/SystemModelSecurityAdapter.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/SystemModelSecurityAdapter.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/SystemModelSecurityAdapter.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -29,10 +29,7 @@
 
 // Local packages
 import org.mulgara.resolver.spi.NullSecurityAdapter;
-import org.mulgara.resolver.spi.Resolver;
 import org.mulgara.resolver.spi.ResolverSession;
-import org.mulgara.resolver.spi.SecurityAdapter;
-import org.mulgara.server.Session;
 import org.mulgara.store.nodepool.NodePool;
 
 /**

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/TransitiveFunction.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/TransitiveFunction.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/TransitiveFunction.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -37,7 +37,6 @@
 
 // Locally written packages
 import org.mulgara.query.*;
-import org.mulgara.store.statement.StatementStoreException;
 import org.mulgara.store.tuples.LiteralTuples;
 import org.mulgara.store.tuples.Tuples;
 import org.mulgara.store.tuples.TuplesOperations;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/XADatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/XADatabaseSessionUnitTest.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/XADatabaseSessionUnitTest.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -559,6 +559,7 @@
                  null,                            // no security domain
                  new JotmTransactionManagerFactory(),
                  0,                               // default transaction timeout
+                 0,                               // default idle timeout
                  nodePoolFactoryClassName,        // persistent
                  new File(persistenceDirectory, "xaNodePool"),
                  stringPoolFactoryClassName,      // persistent

Modified: trunk/src/jar/resolver-relational/java/org/mulgara/resolver/relational/RelationalResolverUnitTest.java
===================================================================
--- trunk/src/jar/resolver-relational/java/org/mulgara/resolver/relational/RelationalResolverUnitTest.java	2008-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver-relational/java/org/mulgara/resolver/relational/RelationalResolverUnitTest.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -235,6 +235,7 @@
                    null,                            // no security domain
                    new JotmTransactionManagerFactory(),
                    0,                               // default transaction timeout
+                   0,                               // default idle timeout
                    nodePoolFactoryClassName,        // persistent
                    new File(persistenceDirectory, "xaNodePool"),
                    stringPoolFactoryClassName,      // persistent

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-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver-test/java/org/mulgara/resolver/test/TestResolverUnitTest.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -405,6 +405,7 @@
 					null,                             // no security domain
 					transactionManagerFactory,
 					0,                                // default transaction timeout
+					0,                                // default idle timeout
 					nodePoolFactoryClassName,         // persistent
 					new File(persistenceDirectory, "xaNodePool"),
 					stringPoolFactoryClassName,       // persistent

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-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/resolver-view/java/org/mulgara/resolver/view/ViewResolverUnitTest.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -557,6 +557,7 @@
         null,                             // no security domain
         transactionManagerFactory,
         0,                                // default transaction timeout
+        0,                                // default idle timeout
         nodePoolFactoryClassName,         // persistent
         new File(persistenceDirectory, "xaNodePool"),
         stringPoolFactoryClassName,       // persistent

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-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/server-beep/java/org/mulgara/server/beep/BEEPSession.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -580,4 +580,12 @@
   public boolean ping() {
     return true;
   }
+
+  public void setIdleTimeout(long millis) {
+    throw new UnsupportedOperationException("Timeouts not implemented under beep.");
+  }
+
+  public void setTransactionTimeout(long millis) {
+    throw new UnsupportedOperationException("Timeouts 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-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSession.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -347,7 +347,27 @@
    */
   public RemoteXAResource getXAResource() throws QueryException, RemoteException;
   public RemoteXAResource getReadOnlyXAResource() throws QueryException, RemoteException;
-  
+
+  /** 
+   * Set the idle timeout for new transactions. 
+   * 
+   * @param millis  the number of milliseconds a transaction may be idle before being aborted,
+   *                or 0 to use a default timeout.
+   * @throws QueryException 
+   * @throws RemoteException 
+   */
+  public void setIdleTimeout(long millis) throws QueryException, RemoteException;
+
+  /** 
+   * Set the maximum duration for new transactions. 
+   * 
+   * @param millis  the number of milliseconds a transaction may be open before being aborted,
+   *                or 0 to use a default timeout.
+   * @throws QueryException 
+   * @throws RemoteException 
+   */
+  public void setTransactionTimeout(long millis) throws QueryException, RemoteException;
+
   /**
    * Test the connectivity of the remote session.
    * @return <code>true</code> if connectivity was established.

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-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSessionWrapperSession.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -693,7 +693,27 @@
       throw new QueryException("Java RMI failure", re);
     }
   }
-  
+
+  public void setIdleTimeout(long millis) throws QueryException {
+    try {
+      remoteSession.setIdleTimeout(millis);
+      resetRetries();
+    } catch (RemoteException re){
+      testRetry(re);
+      setIdleTimeout(millis);
+    }
+  }
+
+  public void setTransactionTimeout(long millis) throws QueryException {
+    try {
+      remoteSession.setTransactionTimeout(millis);
+      resetRetries();
+    } catch (RemoteException re){
+      testRetry(re);
+      setTransactionTimeout(millis);
+    }
+  }
+
   public boolean ping() throws QueryException {
     try {
       boolean ping = remoteSession.ping();

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-07-18 13:51:21 UTC (rev 1097)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/SessionWrapperRemoteSession.java	2008-07-20 13:01:56 UTC (rev 1098)
@@ -517,7 +517,23 @@
       throw convertToQueryException(t);
     }
   }
-  
+
+  public void setIdleTimeout(long millis) throws QueryException, RemoteException {
+    try {
+      session.setIdleTimeout(millis);
+    } catch (Throwable t) {
+      throw convertToQueryException(t);
+    }
+  }
+
+  public void setTransactionTimeout(long millis) throws QueryException, RemoteException {
+    try {
+      session.setTransactionTimeout(millis);
+    } catch (Throwable t) {
+      throw convertToQueryException(t);
+    }
+  }
+
   public boolean ping() throws QueryException, RemoteException {
     try {
       return session.ping();




More information about the Mulgara-svn mailing list