[Mulgara-svn] r157 - in trunk: conf data jxdata/iTQL/data_types jxdata/iTQL/having_queries jxdata/iTQL/standard_queries jxdata/iTQL/transitive jxdata/iTQL/walk src/jar/jrdf/java/org/mulgara/jrdf src/jar/query/java/org/mulgara/query/rdf src/jar/resolver/java/org/mulgara/resolver src/jar/resolver-filesystem/java/org/mulgara/resolver/filesystem src/jar/resolver-gis/java/org/mulgara/resolver/gis src/jar/resolver-lucene/java/org/mulgara/resolver/lucene src/jar/resolver-memory/java/org/mulgara/resolver/memory src/jar/resolver-nodetype/java/org/mulgara/resolver/nodetype src/jar/resolver-prefix/java/org/mulgara/resolver/prefix src/jar/resolver-relational/java/org/mulgara/resolver/relational src/jar/resolver-spi/java/org/mulgara/content src/jar/resolver-spi/java/org/mulgara/resolver/spi src/jar/resolver-store/java/org/mulgara/resolver/store src/jar/resolver-test/java/org/mulgara/resolver/test src/jar/resolver-url/java/org/mulgara/resolver/url src/jar/resolver-view/java/org/mulgara/resolver/view src/jar/resolver-xsd/java/org/mulgara/resolver/xsd src/jar/server-rmi/java/org/mulgara/server/rmi

andrae at mulgara.org andrae at mulgara.org
Wed Jan 3 21:32:44 UTC 2007


Author: andrae
Date: 2007-01-03 15:32:43 -0600 (Wed, 03 Jan 2007)
New Revision: 157

Added:
   trunk/data/xatest-model1.rdf
   trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/EnlistableResource.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/BootstrapOperation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/TransactionOperation.java
Removed:
   trunk/src/jar/resolver/java/org/mulgara/resolver/AnswerDatabaseSession.java
Modified:
   trunk/conf/log4j-mulgara.xml
   trunk/jxdata/iTQL/data_types/queryResult34.txt
   trunk/jxdata/iTQL/having_queries/queryResult10.txt
   trunk/jxdata/iTQL/having_queries/queryResult15.txt
   trunk/jxdata/iTQL/standard_queries/queryResult17.txt
   trunk/jxdata/iTQL/standard_queries/queryResult18.txt
   trunk/jxdata/iTQL/standard_queries/queryResult3.txt
   trunk/jxdata/iTQL/transitive/result13.txt
   trunk/jxdata/iTQL/transitive/result14.txt
   trunk/jxdata/iTQL/walk/queryResult7.txt
   trunk/src/jar/jrdf/java/org/mulgara/jrdf/JRDFGraphElementFactory.java
   trunk/src/jar/jrdf/java/org/mulgara/jrdf/LocalJRDFSession.java
   trunk/src/jar/query/java/org/mulgara/query/rdf/VariableNodeImpl.java
   trunk/src/jar/resolver-filesystem/java/org/mulgara/resolver/filesystem/FileSystemResolver.java
   trunk/src/jar/resolver-gis/java/org/mulgara/resolver/gis/ReadOnlyGISResolver.java
   trunk/src/jar/resolver-lucene/java/org/mulgara/resolver/lucene/LuceneResolver.java
   trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolver.java
   trunk/src/jar/resolver-nodetype/java/org/mulgara/resolver/nodetype/NodeTypeResolver.java
   trunk/src/jar/resolver-prefix/java/org/mulgara/resolver/prefix/PrefixResolver.java
   trunk/src/jar/resolver-relational/java/org/mulgara/resolver/relational/RelationalResolver.java
   trunk/src/jar/resolver-spi/java/org/mulgara/content/ContentResolver.java
   trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/LocalSession.java
   trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/Resolver.java
   trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolver.java
   trunk/src/jar/resolver-test/java/org/mulgara/resolver/test/TestResolver.java
   trunk/src/jar/resolver-url/java/org/mulgara/resolver/url/URLResolver.java
   trunk/src/jar/resolver-view/java/org/mulgara/resolver/view/ViewResolver.java
   trunk/src/jar/resolver-xsd/java/org/mulgara/resolver/xsd/XSDResolver.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/AnswerOperation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/CacheResolver.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/ConstraintOperations.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/DatabaseOperationContext.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransaction.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/OperationContext.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/StringPoolSession.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/SubqueryAnswer.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/SubqueryAnswerUnitTest.java
   trunk/src/jar/resolver/java/org/mulgara/resolver/TransactionalAnswer.java
   trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/BlankNodeWrapperAnswer.java
Log:
merge -r 114:139 ../braches/xafix-impl

Merges versions 114 through 139 of xafix-impl into trunk.

This reflects the new transaction architecture work committed into HEAD.

The xafix-impl branch is now closed.



Modified: trunk/conf/log4j-mulgara.xml
===================================================================
--- trunk/conf/log4j-mulgara.xml	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/conf/log4j-mulgara.xml	2007-01-03 21:32:43 UTC (rev 157)
@@ -62,10 +62,8 @@
   <category name="org.mulgara.content.rdfxml.Parser">
     <priority value="INFO"/>
   </category>
-  -->
-  <!--
   <category name="org.mulgara.resolver.DatabaseSession">
-    <priority value="debug"/>
+    <priority value="info"/>
   </category>
   -->
 

Copied: trunk/data/xatest-model1.rdf (from rev 139, branches/xafix-impl/data/xatest-model1.rdf)

Modified: trunk/jxdata/iTQL/data_types/queryResult34.txt
===================================================================
--- trunk/jxdata/iTQL/data_types/queryResult34.txt	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/jxdata/iTQL/data_types/queryResult34.txt	2007-01-03 21:32:43 UTC (rev 157)
@@ -1,6 +1,6 @@
 ItqlInterpreter error - org.mulgara.query.QueryException: Could not commit insert
 Caused by: (QueryException) Could not commit insert
-Caused by: (QueryException) javax.transaction.RollbackException: null
+Caused by: (QueryException) org.mulgara.resolver.MulgaraTransactionException: Failed transaction finalised. (ROLLBACK)
 Caused by: (QueryException) org.mulgara.resolver.spi.ResolverException: Unable to read input statements
 Caused by: (QueryException) org.mulgara.query.TuplesException: Failed to localize node
 Caused by: (QueryException) org.mulgara.resolver.spi.LocalizeException: Unable to localize "<test>test escape char</foo>"^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral> - Couldn't convert Node to SPObject

Modified: trunk/jxdata/iTQL/having_queries/queryResult10.txt
===================================================================
--- trunk/jxdata/iTQL/having_queries/queryResult10.txt	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/jxdata/iTQL/having_queries/queryResult10.txt	2007-01-03 21:32:43 UTC (rev 157)
@@ -1,5 +1,5 @@
-ItqlInterpreter error - org.mulgara.query.QueryException: Error ending previous query
-Caused by: (QueryException) Error ending previous query
-Caused by: (QueryException) javax.transaction.RollbackException: null
+ItqlInterpreter error - org.mulgara.query.QueryException: Query failed
+Caused by: (QueryException) Query failed
+Caused by: (QueryException) org.mulgara.resolver.MulgaraTransactionException: Failed transaction finalised. (ROLLBACK)
 Caused by: (QueryException) Failed to resolve query
 Caused by: (QueryException) org.mulgara.query.TuplesException: No such variable $v in tuples [$s, $k0] (class org.mulgara.resolver.AppendAggregateTuples)

Modified: trunk/jxdata/iTQL/having_queries/queryResult15.txt
===================================================================
--- trunk/jxdata/iTQL/having_queries/queryResult15.txt	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/jxdata/iTQL/having_queries/queryResult15.txt	2007-01-03 21:32:43 UTC (rev 157)
@@ -1,5 +1,5 @@
-ItqlInterpreter error - org.mulgara.query.QueryException: Error ending previous query
-Caused by: (QueryException) Error ending previous query
-Caused by: (QueryException) javax.transaction.RollbackException: null
+ItqlInterpreter error - org.mulgara.query.QueryException: Query failed
+Caused by: (QueryException) Query failed
+Caused by: (QueryException) org.mulgara.resolver.MulgaraTransactionException: Failed transaction finalised. (ROLLBACK)
 Caused by: (QueryException) Failed to resolve query
 Caused by: (QueryException) org.mulgara.query.TuplesException: No such variable $k0 in tuples [$p, $v, $s] (class org.mulgara.resolver.AppendAggregateTuples)

Modified: trunk/jxdata/iTQL/standard_queries/queryResult17.txt
===================================================================
--- trunk/jxdata/iTQL/standard_queries/queryResult17.txt	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/jxdata/iTQL/standard_queries/queryResult17.txt	2007-01-03 21:32:43 UTC (rev 157)
@@ -1,4 +1,4 @@
 ItqlInterpreter error - org.mulgara.query.QueryException: Could not commit insert
 Caused by: (QueryException) Could not commit insert
-Caused by: (QueryException) javax.transaction.RollbackException: null
+Caused by: (QueryException) org.mulgara.resolver.MulgaraTransactionException: Failed transaction finalised. (ROLLBACK)
 Caused by: (QueryException) rmi://localhost/server1#nomodelexistswiththisname is not a Model

Modified: trunk/jxdata/iTQL/standard_queries/queryResult18.txt
===================================================================
--- trunk/jxdata/iTQL/standard_queries/queryResult18.txt	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/jxdata/iTQL/standard_queries/queryResult18.txt	2007-01-03 21:32:43 UTC (rev 157)
@@ -1,5 +1,5 @@
-ItqlInterpreter error - org.mulgara.query.QueryException: Error ending previous query
-Caused by: (QueryException) Error ending previous query
-Caused by: (QueryException) javax.transaction.RollbackException: null
+ItqlInterpreter error - org.mulgara.query.QueryException: Query failed
+Caused by: (QueryException) Query failed
+Caused by: (QueryException) org.mulgara.resolver.MulgaraTransactionException: Failed transaction finalised. (ROLLBACK)
 Caused by: (QueryException) Error resolving [$s $p $o $_from] from mailto:foo at bar.com
 Caused by: (QueryException) Unable to extract hostname from: mailto:foo at bar.com

Modified: trunk/jxdata/iTQL/standard_queries/queryResult3.txt
===================================================================
--- trunk/jxdata/iTQL/standard_queries/queryResult3.txt	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/jxdata/iTQL/standard_queries/queryResult3.txt	2007-01-03 21:32:43 UTC (rev 157)
@@ -1,5 +1,5 @@
-ItqlInterpreter error - org.mulgara.query.QueryException: Error ending previous query
-Caused by: (QueryException) Error ending previous query
-Caused by: (QueryException) javax.transaction.RollbackException: null
+ItqlInterpreter error - org.mulgara.query.QueryException: Query failed
+Caused by: (QueryException) Query failed
+Caused by: (QueryException) org.mulgara.resolver.MulgaraTransactionException: Failed transaction finalised. (ROLLBACK)
 Caused by: (QueryException) Error resolving [$pmid http://mulgara.org/mulgara/Document#subject "Birds" $_from] from @server@#badmodel
 Caused by: (QueryException) @server@#badmodel is not a Model

Modified: trunk/jxdata/iTQL/transitive/result13.txt
===================================================================
--- trunk/jxdata/iTQL/transitive/result13.txt	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/jxdata/iTQL/transitive/result13.txt	2007-01-03 21:32:43 UTC (rev 157)
@@ -1,4 +1,4 @@
-ItqlInterpreter error - org.mulgara.query.QueryException: Error ending previous query
-Caused by: (QueryException) Error ending previous query
-Caused by: (QueryException) javax.transaction.RollbackException: null
+ItqlInterpreter error - org.mulgara.query.QueryException: Query failed
+Caused by: (QueryException) Query failed
+Caused by: (QueryException) org.mulgara.resolver.MulgaraTransactionException: Failed transaction finalised. (ROLLBACK)
 Caused by: (QueryException) Predicate in transitive function, $p, currently must be bound to a value.

Modified: trunk/jxdata/iTQL/transitive/result14.txt
===================================================================
--- trunk/jxdata/iTQL/transitive/result14.txt	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/jxdata/iTQL/transitive/result14.txt	2007-01-03 21:32:43 UTC (rev 157)
@@ -1,4 +1,4 @@
-ItqlInterpreter error - org.mulgara.query.QueryException: Error ending previous query
-Caused by: (QueryException) Error ending previous query
-Caused by: (QueryException) javax.transaction.RollbackException: null
+ItqlInterpreter error - org.mulgara.query.QueryException: Query failed
+Caused by: (QueryException) Query failed
+Caused by: (QueryException) org.mulgara.resolver.MulgaraTransactionException: Failed transaction finalised. (ROLLBACK)
 Caused by: (QueryException) Predicate in transitive function, $p, currently must be bound to a value.

Modified: trunk/jxdata/iTQL/walk/queryResult7.txt
===================================================================
--- trunk/jxdata/iTQL/walk/queryResult7.txt	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/jxdata/iTQL/walk/queryResult7.txt	2007-01-03 21:32:43 UTC (rev 157)
@@ -1,6 +1,8 @@
 ItqlInterpreter error - org.mulgara.query.QueryException: Error getting information for answer
 Caused by: (QueryException) Error getting information for answer
+Caused by: (QueryException) org.mulgara.query.TuplesException: Transaction error
+Caused by: (QueryException) org.mulgara.resolver.MulgaraTransactionException: Transaction already in rollback
+Caused by: (QueryException) org.mulgara.resolver.MulgaraTransactionException: Failed transaction finalised. (ROLLBACK)
 Caused by: (QueryException) org.mulgara.query.TuplesException: Couldn't evaluate aggregate
 Caused by: (QueryException) Failed to resolve subquery
-Caused by: (QueryException) Failure ending previous query
 Caused by: (QueryException) The subject: $head and the object: $n are invalid, one must be a variable and the other a fixed value around a predicate.

Modified: trunk/src/jar/jrdf/java/org/mulgara/jrdf/JRDFGraphElementFactory.java
===================================================================
--- trunk/src/jar/jrdf/java/org/mulgara/jrdf/JRDFGraphElementFactory.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/jrdf/java/org/mulgara/jrdf/JRDFGraphElementFactory.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -63,11 +63,10 @@
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
 public class JRDFGraphElementFactory implements GraphElementFactory {
-
   /**
    * Logger. This is named after the class.
    */
-  private final static Logger logger =
+  private static final Logger logger =
       Logger.getLogger(JRDFGraphElementFactory.class.getName());
 
   /**
@@ -88,65 +87,19 @@
    * Returns a new BlankNode.
    *
    * @return BlankNode
-   * @throws GraphElementFactoryException
    */
-  public BlankNode createResource() throws GraphElementFactoryException {
-    try {
-
-      // Check to see if we're in a transaction.  If not create a new
-      // transaction and perform the operation.
-      try {
-        jrdfSession.resumeTransactionalBlock();
-      }
-      catch (IllegalStateException ise) {
-        jrdfSession.startTransactionalOperation(true);
-
-        try {
-          return createBlankNode();
-        }
-        catch (Throwable e) {
-          jrdfSession.rollbackTransactionalBlock(e);
-        }
-        finally {
-          jrdfSession.finishTransactionalOperation("Could not commit insert");
-        }
-      }
-
-      // If no exception was thrown then we have resumed the transaction.
-      try {
-        return createBlankNode();
-      }
-      catch (Throwable e) {
-        jrdfSession.rollbackTransactionalBlock(e);
-      }
-      finally {
-        try {
-          jrdfSession.suspendTransactionalBlock();
-        }
-        catch (Throwable e) {
-          jrdfSession.rollbackTransactionalBlock(e);
-        }
-      }
-    }
-    catch (QueryException e) {
-      throw new GraphElementFactoryException(e);
-    }
-    // This should never happen
-    throw new GraphElementFactoryException("Failed to create blank node");
+  public BlankNode createResource() {
+    return createBlankNode();
   }
 
   /**
-   * Create a new blank node - assume current transaction and valid
-   * ResolverSession.
+   * Create a new blank node .
    *
    * @return a newly minted blank node.
-   * @throws LocalizeException if there was a failure in localizing the blank
-   *   node.
    */
-  public BlankNode createBlankNode() throws LocalizeException {
-    BlankNode tmpBlankNode = new BlankNodeImpl();
-    jrdfSession.getResolverSession().localizePersistent(tmpBlankNode);
-    return tmpBlankNode;
+  public BlankNode createBlankNode() {
+    BlankNode node = new BlankNodeImpl();
+    return node;
   }
 
   /**

Modified: trunk/src/jar/jrdf/java/org/mulgara/jrdf/LocalJRDFSession.java
===================================================================
--- trunk/src/jar/jrdf/java/org/mulgara/jrdf/LocalJRDFSession.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/jrdf/java/org/mulgara/jrdf/LocalJRDFSession.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -49,6 +49,6 @@
  *
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
-public interface LocalJRDFSession extends LocalSession, JRDFSession {
+public interface LocalJRDFSession extends JRDFSession {
 
 }

Modified: trunk/src/jar/query/java/org/mulgara/query/rdf/VariableNodeImpl.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/rdf/VariableNodeImpl.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/query/java/org/mulgara/query/rdf/VariableNodeImpl.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -75,11 +75,6 @@
   private final String variableName;
 
   /**
-   * The unique blank node mapping.
-   */
-  private BlankNodeImpl blankNode = null;
-
-  /**
    * Create a new variableNode.
    *
    * @param newVariableName the variable name to set.
@@ -100,26 +95,6 @@
   }
 
   /**
-   * Sets the blank node for variable.
-   *
-   * @param newBlankNode the blank node for the variable.
-   */
-  public void setBlankNode(final BlankNodeImpl newBlankNode) {
-
-    blankNode = newBlankNode;
-  }
-
-  /**
-   * Returns the current blank node.
-   *
-   * @return the current blank node.
-   */
-  public BlankNodeImpl getBlankNode() {
-
-    return blankNode;
-  }
-
-  /**
    * Return itself - variables are immutable.
    *
    * @return a copy.
@@ -180,6 +155,6 @@
    */
   public String toString() {
 
-    return "_variable " + variableName + " :_node" + blankNode;
+    return "_variable " + variableName;
   }
 }

Added: trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -0,0 +1,1061 @@
+/*
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * The Original Code is the Kowari Metadata Store.
+ *
+ * The Initial Developer of the Original Code is Plugged In Software Pty
+ * Ltd (http://www.pisoftware.com, mailto:info at pisoftware.com). Portions
+ * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
+ * Plugged In Software Pty Ltd. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   All tests in this file (c) Netymon Pty Ltd 2006 All rights reserved.
+ *
+ * [NOTE: The text of this Exhibit A may differ slightly from the text
+ * of the notices in the Source Code files of the Original Code. You
+ * should use the text of this Exhibit A rather than the text found in the
+ * Original Code Source Code for Your Modifications.]
+ *
+ */
+
+package org.mulgara.resolver;
+
+// Java 2 standard packages
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+
+// Third party packages
+import junit.framework.*;        // JUnit
+import org.apache.log4j.Logger;  // Log4J
+import org.jrdf.vocabulary.RDF;  // JRDF
+import org.jrdf.graph.SubjectNode;  // JRDF
+import org.jrdf.graph.PredicateNode;  // JRDF
+import org.jrdf.graph.ObjectNode;  // JRDF
+
+// 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.store.StoreException;
+import org.mulgara.store.nodepool.NodePool;
+import org.mulgara.store.stringpool.StringPool;
+import org.mulgara.util.FileUtil;
+
+/**
+* Test case for {@link DatabaseSession}.
+*
+* @created 2006-11-20
+* @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+* @company <a href="mailto:mail at netymon.com">Netymon Pty Ltd</a>
+* @copyright &copy; 2004 <a href="http://www.PIsoftware.com/">Plugged In
+*      Software Pty Ltd</a>
+* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
+*/
+public class AdvDatabaseSessionUnitTest extends TestCase
+{
+  /** Logger.  */
+  private static Logger logger =
+    Logger.getLogger(AdvDatabaseSessionUnitTest.class.getName());
+
+  private static final URI databaseURI;
+
+  private static final URI systemModelURI;
+
+  private static final URI modelURI;
+  private static final URI model2URI;
+  private static final URI model3URI;
+
+  static {
+    try {
+      databaseURI    = new URI("local:database");
+      systemModelURI = new URI("local:database#");
+      modelURI       = new URI("local:database#model");
+      model2URI      = new URI("local:database#model2");
+      model3URI      = new URI("local:database#model3");
+    } catch (URISyntaxException e) {
+      throw new Error("Bad hardcoded URI", e);
+    }
+  }
+
+  private static Database database = null;
+
+  public AdvDatabaseSessionUnitTest(String name)
+  {
+    super(name);
+  }
+
+  public static Test suite()
+  {
+    TestSuite suite = new TestSuite();
+    suite.addTest(new AdvDatabaseSessionUnitTest("testSetModel"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testBasicQuery"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testConcurrentQuery"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testSubqueryQuery"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testConcurrentSubqueryQuery"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testConcurrentReadWrite"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitBasicQuery"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitIsolationQuery"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitRollbackIsolationQuery"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testExplicitCommitIsolationQuery"));
+    suite.addTest(new AdvDatabaseSessionUnitTest("testDatabaseDelete"));
+
+    return suite;
+  }
+
+  /**
+   * Create test objects.
+   */
+  public void setUp() throws Exception {
+    if (database == null) {
+      // Create the persistence directory
+      File persistenceDirectory =
+        new File(new File(System.getProperty("cvs.root")), "testDatabase");
+      if (persistenceDirectory.isDirectory()) {
+        if (!FileUtil.deleteDirectory(persistenceDirectory)) {
+          throw new RuntimeException(
+            "Unable to remove old directory " + persistenceDirectory
+          );
+        }
+      }
+      if (!persistenceDirectory.mkdirs()) {
+        throw new Exception("Unable to create directory "+persistenceDirectory);
+      }
+
+      // Define the the node pool factory
+      String nodePoolFactoryClassName =
+        "org.mulgara.store.nodepool.xa.XANodePoolFactory";
+
+      // Define the string pool factory
+      String stringPoolFactoryClassName =
+        "org.mulgara.store.stringpool.xa.XAStringPoolFactory";
+
+      String tempNodePoolFactoryClassName =
+        "org.mulgara.store.nodepool.memory.MemoryNodePoolFactory";
+
+      // Define the string pool factory
+      String tempStringPoolFactoryClassName =
+        "org.mulgara.store.stringpool.memory.MemoryStringPoolFactory";
+
+      // Define the resolver factory used to manage system models
+      String systemResolverFactoryClassName =
+        "org.mulgara.resolver.store.StatementStoreResolverFactory";
+
+      // Define the resolver factory used to manage system models
+      String tempResolverFactoryClassName =
+        "org.mulgara.resolver.memory.MemoryResolverFactory";
+
+      String ruleLoaderFactoryClassName =
+        "org.mulgara.rules.RuleLoaderFactory";
+
+      // Create a database which keeps its system models on the Java heap
+      database = new Database(
+                   databaseURI,
+                   persistenceDirectory,
+                   null,                            // no security domain
+                   new JotmTransactionManagerFactory(),
+                   0,                               // default transaction timeout
+                   nodePoolFactoryClassName,        // persistent
+                   new File(persistenceDirectory, "xaNodePool"),
+                   stringPoolFactoryClassName,      // persistent
+                   new File(persistenceDirectory, "xaStringPool"),
+                   systemResolverFactoryClassName,  // persistent
+                   new File(persistenceDirectory, "xaStatementStore"),
+                   tempNodePoolFactoryClassName,    // temporary nodes
+                   null,                            // no dir for temp nodes
+                   tempStringPoolFactoryClassName,  // temporary strings
+                   null,                            // no dir for temp strings
+                   tempResolverFactoryClassName,    // temporary models
+                   null,                            // no dir for temp models
+                   "",                              // no rule loader
+                   "org.mulgara.content.rdfxml.RDFXMLContentHandler");
+
+      database.addResolverFactory("org.mulgara.resolver.url.URLResolverFactory", null);
+    }
+  }
+
+
+  /**
+  * The teardown method for JUnit
+  */
+  public void tearDown()
+  {
+  }
+
+  //
+  // Test cases
+  //
+
+  /**
+  * Test the {@link DatabaseSession#setModel} method.
+  */
+  public void testSetModel() throws URISyntaxException
+  {
+    logger.info("testSetModel");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      // Load some test data
+      Session session = database.newSession();
+      try {
+        session.createModel(modelURI, null);
+        session.setModel(modelURI, new ModelResource(fileURI));
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  public void testBasicQuery() throws URISyntaxException {
+    logger.info("Testing basicQuery");
+
+    try {
+      // 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 selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Arrays.asList(new Order[] {                       // ORDER BY
+            new Order(subjectVariable, true),
+            new Order(predicateVariable, true),
+            new Order(objectVariable, true)
+          }),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+        String[][] results = {
+          { "test:s01", "test:p01", "test:o01" },
+          { "test:s01", "test:p02", "test:o01" },
+          { "test:s01", "test:p02", "test:o02" },
+          { "test:s01", "test:p03", "test:o02" },
+          { "test:s02", "test:p03", "test:o02" },
+          { "test:s02", "test:p04", "test:o02" },
+          { "test:s02", "test:p04", "test:o03" },
+          { "test:s02", "test:p05", "test:o03" },
+          { "test:s03", "test:p01", "test:o01" },
+          { "test:s03", "test:p05", "test:o03" },
+          { "test:s03", "test:p06", "test:o01" },
+          { "test:s03", "test:p06", "test:o03" },
+        };
+        compareResults(results, answer);
+        answer.close();
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  
+  public void testConcurrentQuery() throws URISyntaxException {
+    logger.info("Testing concurrentQuery");
+
+    try {
+      // Load some test data
+      Session session = database.newSession();
+      try {
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(predicateVariable);
+        selectList.add(objectVariable);
+
+        // Evaluate the query
+        Answer answer1 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        Answer answer2 = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        compareResults(answer1, answer2);
+
+        answer1.close();
+        answer2.close();
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  
+  public void testSubqueryQuery() throws URISyntaxException {
+    logger.info("Testing subqueryQuery");
+
+    try {
+      // Load some test data
+      Session session = database.newSession();
+      try {
+        Variable subjectVariable   = new Variable("subject");
+        Variable predicateVariable = new Variable("predicate");
+        Variable objectVariable    = new Variable("object");
+
+        List selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(new Subquery(new Variable("k0"), new Query(
+          Collections.singletonList(objectVariable),
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(objectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        )));
+
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+              new URIReferenceImpl(new URI("test:p03")),
+              objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        answer.beforeFirst();
+
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:s01")),
+            answer.getObject(0));
+        Answer sub1 = (Answer)answer.getObject(1);
+        compareResults(new String[][] { new String[] { "test:o01" },
+                                        new String[] { "test:o02" } }, sub1);
+        sub1.close();
+
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:s02")),
+            answer.getObject(0));
+        Answer sub2 = (Answer)answer.getObject(1);
+        compareResults(new String[][] { new String[] { "test:o02" },
+                                        new String[] { "test:o03" } }, sub2);
+        // Leave sub2 open.
+
+        assertFalse(answer.next());
+        answer.close();
+        sub2.close();
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  
+  public void testConcurrentSubqueryQuery() throws URISyntaxException {
+    logger.info("Testing concurrentSubqueryQuery");
+
+    try {
+      // 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 selectList = new ArrayList(3);
+        selectList.add(subjectVariable);
+        selectList.add(new Subquery(new Variable("k0"), new Query(
+          Collections.singletonList(objectVariable),
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+                         predicateVariable,
+                         objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(objectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        )));
+
+
+        // Evaluate the query
+        Answer answer = session.query(new Query(
+          selectList,                                       // SELECT
+          new ModelResource(modelURI),                      // FROM
+          new ConstraintImpl(subjectVariable,               // WHERE
+              new URIReferenceImpl(new URI("test:p03")),
+              objectVariable),
+          null,                                             // HAVING
+          Collections.singletonList(                        // ORDER BY
+            new Order(subjectVariable, true)
+          ),
+          null,                                             // LIMIT
+          0,                                                // OFFSET
+          new UnconstrainedAnswer()                         // GIVEN
+        ));
+
+        answer.beforeFirst();
+
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:s01")),
+            answer.getObject(0));
+        Answer sub1 = (Answer)answer.getObject(1);
+        assertTrue(answer.next());
+        assertEquals(new URIReferenceImpl(new URI("test:s02")),
+            answer.getObject(0));
+        Answer sub2 = (Answer)answer.getObject(1);
+        assertFalse(answer.next());
+
+        assertEquals(1, sub1.getNumberOfVariables());
+        assertEquals(1, sub2.getNumberOfVariables());
+        sub1.beforeFirst();
+        sub2.beforeFirst();
+        assertTrue(sub1.next());
+        assertTrue(sub2.next());
+        assertEquals(new URIReferenceImpl(new URI("test:o01")), sub1.getObject(0));
+        assertEquals(new URIReferenceImpl(new URI("test:o02")), sub2.getObject(0));
+        assertTrue(sub1.next());
+        assertTrue(sub2.next());
+        assertEquals(new URIReferenceImpl(new URI("test:o02")), sub1.getObject(0));
+        assertEquals(new URIReferenceImpl(new URI("test:o03")), sub2.getObject(0));
+        assertFalse(sub1.next());
+        assertFalse(sub2.next());
+
+        answer.close();
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  
+  /**
+   * Note: What this test does is a really bad idea - there is no
+   *       isolation provided as each operation is within its own
+   *       transaction.  It does however provide a good test.
+   */
+  public void testConcurrentReadWrite() throws URISyntaxException {
+    logger.info("Testing concurrentReadWrite");
+
+    try {
+      Session session = database.newSession();
+
+      session.createModel(model2URI, null);
+
+      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 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.beforeFirst();
+        while (answer.next()) {
+          session.insert(model2URI, Collections.singleton(new TripleImpl(
+              (SubjectNode)answer.getObject(0),
+              (PredicateNode)answer.getObject(1),
+              (ObjectNode)answer.getObject(2))));
+        }
+        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);
+        answer2.close();
+
+        session.removeModel(model2URI);
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  
+  public void testExplicitBasicQuery() throws URISyntaxException {
+    logger.info("Testing basicQuery");
+
+    try {
+      // Load some test data
+      Session session = database.newSession();
+      try {
+        session.setAutoCommit(false);
+        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);
+
+        session.setAutoCommit(true);
+
+        // Should throw an Exception here as commit should close answer
+        boolean thrown = false;;
+        try {
+          answer.beforeFirst();
+        } catch (TuplesException et) {
+          thrown = true;
+        } finally {
+          assertTrue("Answer failed to throw exception, should be closed by commit",
+              thrown);
+        }
+      } finally {
+        session.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+  
+  public void testExplicitIsolationQuery() throws URISyntaxException
+  {
+    logger.info("testExplicitIsolationQuery");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        Session session2 = database.newSession();
+        try {
+          session1.createModel(model3URI, null);
+          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 selectList = new ArrayList(3);
+          selectList.add(subjectVariable);
+          selectList.add(predicateVariable);
+          selectList.add(objectVariable);
+
+          // Evaluate the query
+          Answer answer = session2.query(new Query(
+            selectList,                                       // SELECT
+            new ModelResource(model3URI),                      // FROM
+            new ConstraintImpl(subjectVariable,               // WHERE
+                           predicateVariable,
+                           objectVariable),
+            null,                                             // HAVING
+            Arrays.asList(new Order[] {                       // ORDER BY
+              new Order(subjectVariable, true),
+              new Order(predicateVariable, true),
+              new Order(objectVariable, true)
+            }),
+            null,                                             // LIMIT
+            0,                                                // OFFSET
+            new UnconstrainedAnswer()                         // GIVEN
+          ));
+          answer.beforeFirst();
+          assertFalse(answer.next());
+          answer.close();
+
+          session1.setAutoCommit(true);
+
+          selectList = new ArrayList(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.close();
+
+          session1.removeModel(model3URI);
+        } finally {
+          session2.close();
+        }
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  public void testExplicitRollbackIsolationQuery() throws URISyntaxException
+  {
+    logger.info("testExplicitRollbackIsolationQuery");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        Session session2 = database.newSession();
+        try {
+          session1.createModel(model3URI, null);
+          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 selectList = new ArrayList(3);
+          selectList.add(subjectVariable);
+          selectList.add(predicateVariable);
+          selectList.add(objectVariable);
+
+          // Evaluate the query
+          Answer answer = session2.query(new Query(
+            selectList,                                       // SELECT
+            new ModelResource(model3URI),                      // FROM
+            new ConstraintImpl(subjectVariable,               // WHERE
+                           predicateVariable,
+                           objectVariable),
+            null,                                             // HAVING
+            Arrays.asList(new Order[] {                       // ORDER BY
+              new Order(subjectVariable, true),
+              new Order(predicateVariable, true),
+              new Order(objectVariable, true)
+            }),
+            null,                                             // LIMIT
+            0,                                                // OFFSET
+            new UnconstrainedAnswer()                         // GIVEN
+          ));
+          answer.beforeFirst();
+          assertFalse(answer.next());
+          answer.close();
+
+          session1.rollback();
+          session1.setAutoCommit(true);
+
+          selectList = new ArrayList(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.beforeFirst();
+          assertFalse(answer.next());
+          answer.close();
+        } finally {
+          session2.close();
+        }
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  public void testExplicitCommitIsolationQuery() throws URISyntaxException
+  {
+    logger.info("testExplicitCommitIsolationQuery");
+    URI fileURI  = new File("data/xatest-model1.rdf").toURI();
+
+    try {
+      Session session1 = database.newSession();
+      try {
+        Session session2 = database.newSession();
+        try {
+          session1.createModel(model3URI, null);
+          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 selectList = new ArrayList(3);
+          selectList.add(subjectVariable);
+          selectList.add(predicateVariable);
+          selectList.add(objectVariable);
+
+          // Evaluate the query
+          Answer answer = session2.query(new Query(
+            selectList,                                       // SELECT
+            new ModelResource(model3URI),                      // FROM
+            new ConstraintImpl(subjectVariable,               // WHERE
+                           predicateVariable,
+                           objectVariable),
+            null,                                             // HAVING
+            Arrays.asList(new Order[] {                       // ORDER BY
+              new Order(subjectVariable, true),
+              new Order(predicateVariable, true),
+              new Order(objectVariable, true)
+            }),
+            null,                                             // LIMIT
+            0,                                                // OFFSET
+            new UnconstrainedAnswer()                         // GIVEN
+          ));
+          answer.beforeFirst();
+          assertFalse(answer.next());
+          answer.close();
+
+          session1.commit();
+
+          selectList = new ArrayList(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.close();
+
+          session1.removeModel(model3URI);
+          session1.createModel(model3URI, null);
+
+          selectList = new ArrayList(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
+          ));
+
+          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);
+          answer.close();
+
+          session1.setAutoCommit(true);
+
+          selectList = new ArrayList(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.beforeFirst();
+          assertFalse(answer.next());
+          answer.close();
+        } finally {
+          session2.close();
+        }
+      } finally {
+        session1.close();
+      }
+    } catch (Exception e) {
+      fail(e);
+    }
+  }
+
+
+  public void testDatabaseDelete() {
+    database.delete();
+    database = null;
+  }
+
+  //
+  // Internal methods
+  //
+
+  private void compareResults(String[][] expected, Answer answer) throws Exception {
+    answer.beforeFirst();
+    for (int i = 0; i < expected.length; i++) {
+      assertTrue("Answer short at row " + i, answer.next());
+      assertEquals(expected[i].length, answer.getNumberOfVariables());
+      for (int j = 0; j < expected[i].length; j++) {
+        URIReferenceImpl uri = new URIReferenceImpl(
+            new URI(expected[i][j]));
+        assertEquals(uri, answer.getObject(j));
+      }
+    }
+    assertFalse(answer.next());
+  }
+
+  private void compareResults(Answer answer1, Answer answer2) throws Exception {
+    answer1.beforeFirst();
+    answer2.beforeFirst();
+    assertEquals(answer1.getNumberOfVariables(), answer2.getNumberOfVariables());
+    while (answer1.next()) {
+      assertTrue(answer2.next());
+      for (int i = 0; i < answer1.getNumberOfVariables(); i++) {
+        assertEquals(answer1.getObject(i), answer2.getObject(i));
+      }
+    }
+    assertFalse(answer2.next());
+  }
+
+  /**
+   * Fail with an unexpected exception
+   */
+  private void fail(Throwable throwable)
+  {
+    StringWriter stringWriter = new StringWriter();
+    throwable.printStackTrace(new PrintWriter(stringWriter));
+    fail(stringWriter.toString());
+  }
+}

Deleted: trunk/src/jar/resolver/java/org/mulgara/resolver/AnswerDatabaseSession.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/AnswerDatabaseSession.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/AnswerDatabaseSession.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -1,77 +0,0 @@
-/*
- * The contents of this file are subject to the Mozilla Public License
- * Version 1.1 (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.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS"
- * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
- * the License for the specific language governing rights and limitations
- * under the License.
- *
- * The Original Code is the Kowari Metadata Store.
- *
- * The Initial Developer of the Original Code is Plugged In Software Pty
- * Ltd (http://www.pisoftware.com, mailto:info at pisoftware.com). Portions
- * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
- * Plugged In Software Pty Ltd. All Rights Reserved.
- *
- * Contributor(s): N/A.
- *
- * [NOTE: The text of this Exhibit A may differ slightly from the text
- * of the notices in the Source Code files of the Original Code. You
- * should use the text of this Exhibit A rather than the text found in the
- * Original Code Source Code for Your Modifications.]
- *
- */
-
-package org.mulgara.resolver;
-
-// Local packages
-import org.mulgara.query.*;
-
-/**
- * Defines the extra requirements of {@link Answer}s upon
- * {@link DatabaseSession}.
- *
- * @created 2004-10-02
- *
- * @author Andrew Newman
- *
- * @version $Revision: 1.8 $
- *
- * @modified $Date: 2005/01/05 04:58:23 $ by $Author: newmana $
- *
- * @maintenanceAuthor $Author: newmana $
- *
- * @copyright &copy;2004 <a href="http://www.tucanatech.com/">Tucana
- *   Technology, Inc</a>
- *
- * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
- */
-public interface AnswerDatabaseSession {
-
-  /**
-   * Performs a query within an existing transaction.
-   *
-   * @param query the query to perform.
-   * @return a non-<code>null</code> answer to the <var>query</var>
-   * @throws QueryException if <var>query</var> can't be answered
-   */
-  public Answer innerQuery(Query query) throws QueryException;
-
-  /**
-   * Answers are registered so that the session can ensure that they are closed.
-   *
-   * @param answer the answer to register with this session.
-   */
-  public void registerAnswer(SubqueryAnswer answer);
-
-  /**
-   * Answers are deregistered to indicate that they have closed themselves.
-   *
-   * @param answer the answer to deregister.
-   * @throws QueryException if the transactional block for this answer fails.
-   */
-  public void deregisterAnswer(SubqueryAnswer answer) throws QueryException;
-}

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/AnswerOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/AnswerOperation.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/AnswerOperation.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -21,12 +21,13 @@
 
 public abstract class AnswerOperation {
   // Should use enum here.
+  public static final int VOID = 0;
   public static final int OBJECT = 1;
   public static final int INT = 2;
   public static final int LONG = 3;
   public static final int BOOLEAN = 3;
 
-  protected int returnType;
+  protected int returnType = VOID;
 
   protected Object object;
   protected int integer;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -195,7 +195,7 @@
   * of the system model in the newly-created {@link Database}.
   */
   public void testQuery1() {
-    logger.debug("Testing testQuery1");
+    logger.info("TestQuery1");
     try {
       // Test querying the system model (#)
       Session session = database.newSession();
@@ -225,6 +225,7 @@
             new UnconstrainedAnswer()           // GIVEN
           )));
 
+
         // Compose the expected result of the query
         Answer expectedAnswer = new ArrayAnswer(
             new Variable[] { subjectVariable, predicateVariable, objectVariable },
@@ -250,7 +251,7 @@
   */
   public void testSetModel() throws URISyntaxException
   {
-    logger.debug("Testing testSetModel");
+    logger.info("testSetModel");
     URI fileURI  = new File("data/dc.rdfs").toURI();
     URI modelURI = new URI("local:database#model");
 
@@ -278,7 +279,7 @@
   */
 
   public void testQuery2() throws URISyntaxException {
-    logger.debug("Testing testQuery2");
+    logger.info("Testing testQuery2");
     URI dcFileURI   = new File("data/dc.rdfs").toURI();
     URI rdfsFileURI = new File("data/rdfs.rdfs").toURI();
 

Added: trunk/src/jar/resolver/java/org/mulgara/resolver/BootstrapOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/BootstrapOperation.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/BootstrapOperation.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -0,0 +1,96 @@
+package org.mulgara.resolver;
+
+// Java 2 standard packages
+
+// Java 2 enterprise packages
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+// 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;
+
+class BootstrapOperation implements Operation
+{
+  /** Logger */
+  private static final Logger logger =
+    Logger.getLogger(BootstrapOperation.class.getName());
+
+  /**
+   * The URI of the model to be created.
+   */
+  private final DatabaseMetadataImpl databaseMetadata;
+
+  private long result;
+
+  BootstrapOperation(DatabaseMetadataImpl databaseMetadata) {
+    if (databaseMetadata == null) {
+      throw new IllegalArgumentException("BootstrapSystemModel - databaseMetadata null ");
+    }
+
+    this.databaseMetadata = databaseMetadata;
+    this.result = -1; // Return invalid node by default.
+  }
+
+  public void execute(OperationContext       operationContext,
+                      SystemResolver         systemResolver,
+                      ResolverSessionFactory resolverSessionFactory,
+                      DatabaseMetadata       metadata) throws Exception {
+    // Find the local node identifying the model
+    long model = systemResolver.localizePersistent(
+        new URIReferenceImpl(databaseMetadata.getSystemModelURI()));
+    long rdfType = systemResolver.localizePersistent(
+        new URIReferenceImpl(databaseMetadata.getRdfTypeURI()));
+    long modelType = systemResolver.localizePersistent(
+        new URIReferenceImpl(databaseMetadata.getSystemModelTypeURI()));
+
+    // Use the session to create the model
+    systemResolver.modifyModel(model, new SingletonStatements(model, rdfType,
+        modelType), true);
+    databaseMetadata.initializeSystemNodes(model, rdfType, modelType);
+
+    long preSubject = systemResolver.localizePersistent(
+        new URIReferenceImpl(databaseMetadata.getPreallocationSubjectURI()));
+    long prePredicate = systemResolver.localizePersistent(
+        new URIReferenceImpl(databaseMetadata.getPreallocationPredicateURI()));
+    long preModel = systemResolver.localizePersistent(
+        new URIReferenceImpl(databaseMetadata.getPreallocationModelURI()));
+
+    // Every node cached by DatabaseMetadata must be preallocated
+    systemResolver.modifyModel(preModel,
+        new SingletonStatements(preSubject, prePredicate, model),
+        true);
+    systemResolver.modifyModel(preModel,
+        new SingletonStatements(preSubject, prePredicate, rdfType),
+        true);
+    systemResolver.modifyModel(preModel,
+        new SingletonStatements(preSubject, prePredicate, modelType),
+        true);
+    systemResolver.modifyModel(preModel,
+        new SingletonStatements(preSubject, prePredicate, preSubject),
+        true);
+    systemResolver.modifyModel(preModel,
+        new SingletonStatements(preSubject, prePredicate, prePredicate),
+        true);
+    systemResolver.modifyModel(preModel,
+        new SingletonStatements(preSubject, prePredicate, preModel),
+        true);
+
+    databaseMetadata.initializePreallocationNodes(preSubject, prePredicate, preModel);
+
+    result = model;
+  }
+
+  public boolean isWriteOperation()
+  {
+    return true;
+  }
+
+  public long getResult() {
+    return result;
+  }
+}

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/CacheResolver.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/CacheResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/CacheResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -225,4 +225,6 @@
 
     return temporaryResolver;
   }
+
+  public void abort() {}
 }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/ConstraintOperations.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/ConstraintOperations.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/ConstraintOperations.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -41,7 +41,6 @@
 
 // Local packages
 import org.mulgara.query.*;
-import org.mulgara.query.rdf.BlankNodeImpl;
 import org.mulgara.query.rdf.LiteralImpl;
 import org.mulgara.query.rdf.URIReferenceImpl;
 import org.mulgara.resolver.spi.ConstraintBindingHandler;

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/CreateModelOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/CreateModelOperation.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/CreateModelOperation.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -212,8 +212,7 @@
     // PREVIOUSLY WITHIN TRANSACTION
 
     // Obtain an appropriate resolver bound to this session
-    Resolver resolver =
-      operationContext.obtainResolver(resolverFactory, systemResolver);
+    Resolver resolver = operationContext.obtainResolver(resolverFactory);
     assert resolver != null;
 
     // Find the local node identifying the model

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/Database.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/Database.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/Database.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -253,10 +253,12 @@
   private final TransactionManagerFactory transactionManagerFactory;
 
   /**
-   * JTS transaction manager used to distribute transactions over multiple
-   * resolvers.
+   * The internal transaction manager.
+   *
+   * This class is a singleton with respect to a database instance.
+   * Passed to new DatabaseSession's.
    */
-  private final TransactionManager transactionManager;
+  private final MulgaraTransactionManager transactionManager;
 
   /** The unique {@link URI} naming this database.  */
   private final URI uri;
@@ -510,17 +512,9 @@
     assert this.contentHandlers != null;
 
     // FIXME: Migrate this code inside StringPoolSession.  Pass config to StringPoolSession.
-    this.transactionManager = transactionManagerFactory.newTransactionManager();
+    this.transactionManager = new MulgaraTransactionManager(transactionManagerFactory);
 
-    // Set the transaction timeout to an hour
-    try {
-      transactionManager.setTransactionTimeout(transactionTimeout);
-    }
-    catch (SystemException e) {
-      logger.warn(
-        "Unable to set transaction timeout to " + transactionTimeout + "s", e
-      );
-    }
+    transactionManager.setTransactionTimeout(transactionTimeout);
 
     // Enable resolver initialization
     if (logger.isDebugEnabled()) {
@@ -676,6 +670,7 @@
       logger.debug("Added system resolver " + systemResolverFactoryClassName);
     }
 
+
     URI systemModelURI = new URI(uri.getScheme(), uri.getSchemeSpecificPart(), "");
     metadata =
       new DatabaseMetadataImpl(uri,

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseOperationContext.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseOperationContext.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseOperationContext.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -34,6 +34,7 @@
 import java.net.URISyntaxException;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -67,6 +68,8 @@
 import org.mulgara.resolver.spi.SymbolicTransformation;
 import org.mulgara.resolver.spi.SymbolicTransformationContext;
 import org.mulgara.resolver.spi.SystemResolver;
+import org.mulgara.resolver.spi.SystemResolverFactory;
+import org.mulgara.resolver.spi.TuplesWrapperStatements;
 import org.mulgara.resolver.view.ViewMarker;
 import org.mulgara.resolver.view.SessionView;
 import org.mulgara.store.nodepool.NodePool;
@@ -86,8 +89,7 @@
  *   Technology, Inc</a>
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
-class DatabaseOperationContext implements OperationContext, SessionView,
-AnswerDatabaseSession, SymbolicTransformationContext
+class DatabaseOperationContext implements OperationContext, SessionView, SymbolicTransformationContext
 {
   /**
    * Logger.
@@ -124,13 +126,19 @@
    * localized type of the model.
    *
    * This is populated by {@link #findModelTypeURI} and cleared by
-   * {@link #clearSystemModelCache}.
+   * clear()
    */
   private final Map systemModelCacheMap = new WeakHashMap();
 
+  /** Resolver used for accessing the system model (<code>#</code>).  */
+  protected SystemResolverFactory systemResolverFactory;
+  protected SystemResolver systemResolver;
+
+  /** The transaction associated with these operations */
+  private MulgaraTransaction transaction;
+
   // Immutable properties of the containing DatabaseSession
   private final Set                cachedResolverFactorySet;
-  private final DatabaseSession    databaseSession;
   private final Map                enlistedResolverMap;
   private final Map                externalResolverFactoryMap;
   private final Map                internalResolverFactoryMap;
@@ -138,63 +146,48 @@
   private final List               securityAdapterList;
   private final URI                temporaryModelTypeURI;
   private final ResolverFactory    temporaryResolverFactory;
-  private final TransactionManager transactionManager;
-  private final Set                outstandingAnswers;
   /** Symbolic transformations this instance should apply. */
-  private final List symbolicTransformationList;
+  private final List               symbolicTransformationList;
+  private final boolean            isWriting;
 
-  //
-  // Constructor
-  //
+  private WeakHashMap answers;  // Used as a set, all values are null.  Java doesn't provide a WeakHashSet.
 
-  /**
-   * Sole constructor.
-   */
-  DatabaseOperationContext(Set                cachedModelSet,
-                           Set                cachedResolverFactorySet,
-                           Set                changedCachedModelSet,
-                           DatabaseSession    databaseSession,
-                           Map                enlistedResolverMap,
-                           Map                externalResolverFactoryMap,
-                           Map                internalResolverFactoryMap,
-                           DatabaseMetadata   metadata,
-                           List               securityAdapterList,
-                           URI                temporaryModelTypeURI,
-                           ResolverFactory    temporaryResolverFactory,
-                           TransactionManager transactionManager,
-                           Set                outstandingAnswers,
-                           List               symbolicTransformationList)
+  DatabaseOperationContext(Set                   cachedResolverFactorySet,
+                           Map                   externalResolverFactoryMap,
+                           Map                   internalResolverFactoryMap,
+                           DatabaseMetadata      metadata,
+                           List                  securityAdapterList,
+                           URI                   temporaryModelTypeURI,
+                           ResolverFactory       temporaryResolverFactory,
+                           List                  symbolicTransformationList,
+                           SystemResolverFactory systemResolverFactory,
+                           boolean               isWriting)
   {
-    assert cachedModelSet             != null;
     assert cachedResolverFactorySet   != null;
-    assert changedCachedModelSet      != null;
-    assert databaseSession            != null;
-    assert enlistedResolverMap        != null;
     assert externalResolverFactoryMap != null;
     assert internalResolverFactoryMap != null;
     assert metadata                   != null;
     assert securityAdapterList        != null;
     assert temporaryModelTypeURI      != null;
     assert temporaryResolverFactory   != null;
-    assert transactionManager         != null;
+    assert symbolicTransformationList != null;
+    assert systemResolverFactory      != null;
 
-    // Initialize fields
-    this.cachedModelSet             = cachedModelSet;
     this.cachedResolverFactorySet   = cachedResolverFactorySet;
-    this.changedCachedModelSet      = changedCachedModelSet;
-    this.databaseSession            = databaseSession;
-    this.enlistedResolverMap        = enlistedResolverMap;
     this.externalResolverFactoryMap = externalResolverFactoryMap;
     this.internalResolverFactoryMap = internalResolverFactoryMap;
     this.metadata                   = metadata;
     this.securityAdapterList        = securityAdapterList;
     this.temporaryModelTypeURI      = temporaryModelTypeURI;
     this.temporaryResolverFactory   = temporaryResolverFactory;
-    this.transactionManager         = transactionManager;
-    // Note this is only temporary - we will be eliminating outstandingAnswers
-    // before the end of the transaction fix.
-    this.outstandingAnswers         = outstandingAnswers;
     this.symbolicTransformationList = symbolicTransformationList;
+    this.isWriting                  = isWriting;
+    this.systemResolverFactory      = systemResolverFactory;
+
+    this.cachedModelSet             = new HashSet();
+    this.changedCachedModelSet      = new HashSet();
+    this.enlistedResolverMap        = new HashMap();
+    this.answers                    = new WeakHashMap();
   }
 
   //
@@ -223,11 +216,10 @@
           throw new QueryException("Unsupported model type for model " + model);        }
 
         return internalResolverFactory;
-      }
-      else {
+      } else {
         // This might be an external model or an aliased internal model.
         // get the model URI
-        Node modelNode = databaseSession.getSystemResolver().globalize(model);
+        Node modelNode = systemResolver.globalize(model);
         if (!(modelNode instanceof URIReference)) {
           throw new QueryException(modelNode.toString() + " is not a valid Model");
         }
@@ -236,7 +228,7 @@
         // check if this is really a reference to a local model, using a different server name
         Node aliasedNode = getCanonicalAlias(modelURI);
         if (aliasedNode != null) {
-          long aliasedModel = databaseSession.getSystemResolver().localize(aliasedNode);
+          long aliasedModel = systemResolver.localize(aliasedNode);
           return findModelResolverFactory(aliasedModel);
         }
 
@@ -271,8 +263,7 @@
               throw new QueryException(modelNode.toString() + " is not a Model");
             }
           }
-        }
-        catch (URISyntaxException use) {
+        } catch (URISyntaxException use) {
           throw new QueryException("Internal error.  Model URI cannot be manipulated.");
         }
 
@@ -300,20 +291,73 @@
                                           temporaryModelTypeURI,
                                           cachedModelSet,
                                           changedCachedModelSet);
-        }
-        else {
+        } else {
           return resolverFactory;
         }
       }
-    }
-    catch (GlobalizeException eg) {
+    } catch (GlobalizeException eg) {
       throw new QueryException("Unable to globalize modeltype", eg);
-    }
-    catch (LocalizeException el) {
+    } catch (LocalizeException el) {
       throw new QueryException("Unable to localize model", el);
     }
   }
 
+  /**
+   * Find a cached resolver factory for write back.
+   *
+   * @return a completely unwrapped resolver factory
+   */
+  // TODO: Common code with findModelResolverFactory should be consolidated.
+  private ResolverFactory findResolverFactory(long model) throws QueryException
+  {
+    if (logger.isDebugEnabled()) {
+      logger.debug("Finding raw resolver factory for model " + model);
+    }
+
+    try {
+      // get the model URI
+      Node modelNode = systemResolver.globalize(model);
+      if (!(modelNode instanceof URIReference)) {
+        throw new QueryException(modelNode.toString() + " is not a valid Model");
+      }
+      URI modelURI = ((URIReference)modelNode).getURI();
+
+      // test the model URI against the current server
+      try {
+        if (logger.isDebugEnabled()) {
+          logger.debug("Comparing " + metadata.getURI().toString() + " to "
+              + (new URI(modelURI.getScheme(), modelURI.getSchemeSpecificPart(), null)).toString());
+        }
+        if (metadata.getURI().equals(
+            new URI(modelURI.getScheme(), modelURI.getSchemeSpecificPart(), null))) {
+          // should be on the current server, but was not found here
+          throw new QueryException(modelNode.toString() + " is not a Model");
+        }
+      } catch (URISyntaxException use) {
+        throw new QueryException("Internal error.  Model URI cannot be manipulated.");
+      }
+
+      // This is not a local model, get the protocol
+      String modelProtocol = findProtocol(model);
+      if (logger.isDebugEnabled()) {
+        logger.debug("Model " + model + " protocol is " + modelProtocol);
+      }
+
+      // find the factory for this protocol
+      ResolverFactory resolverFactory =
+          (ResolverFactory) externalResolverFactoryMap.get(modelProtocol);
+      if (resolverFactory == null) {
+        throw new QueryException(
+            "Unsupported protocol for destination model (" +
+            modelProtocol + ", " + model + " : '" + modelProtocol + "')");
+      }
+
+      return resolverFactory;
+    } catch (GlobalizeException eg) {
+      throw new QueryException("Unable to globalize modeltype", eg);
+    }
+  }
+
   public ResolverFactory findModelTypeResolverFactory(URI modelTypeURI)
     throws QueryException
   {
@@ -325,8 +369,7 @@
     return securityAdapterList;
   }
 
-  public Resolver obtainResolver(ResolverFactory resolverFactory,
-                                 SystemResolver  systemResolver)
+  public Resolver obtainResolver(ResolverFactory resolverFactory)
     throws QueryException
   {
     ResolverSession session;
@@ -338,9 +381,7 @@
     }
 
     try {
-      resolver = resolverFactory.newResolver(databaseSession.isWriting(),
-                                             systemResolver,
-                                             systemResolver);
+      resolver = resolverFactory.newResolver(isWriting, systemResolver, systemResolver);
 
       // FIXME: This is a kludge.  This should be done using a query rewriting
       //        hook in the ResolverFactory interface.  This hook is also
@@ -350,15 +391,23 @@
       if (resolver instanceof ViewMarker) {
         ((ViewMarker) resolver).setSession(this);
       }
-    }
-    catch (ResolverFactoryException e) {
+    } catch (ResolverFactoryException e) {
       throw new QueryException("Unable to obtain resolver", e);
     }
+
     assert resolver != null;
 
+    try {
+      transaction.enlist(resolver);
+    } catch (Exception e) {
+      logger.warn("Failed to enlist resolver, aborting resolver");
+      resolver.abort();
+      throw new QueryException("Unable to enlist " + resolver + " into transaction", e);
+    }
+
     enlistedResolverMap.put(resolverFactory, resolver);
 
-    return enlistResolver(resolver);
+    return resolver;
   }
 
 
@@ -371,7 +420,7 @@
   public long getCanonicalModel(long model) {
     // globalize to a URI
     try {
-      Node modelNode = databaseSession.getSystemResolver().globalize(model);
+      Node modelNode = systemResolver.globalize(model);
       if (!(modelNode instanceof URIReference)) {
         logger.warn(modelNode.toString() + " is not a valid Model");
         return model;
@@ -381,7 +430,7 @@
       // check if this is really a reference to a local model, using a different server name
       Node aliasedNode = getCanonicalAlias(modelURI);
       if (aliasedNode != null) {
-        return databaseSession.getSystemResolver().localize(aliasedNode);
+        return systemResolver.localize(aliasedNode);
       }
     } catch (Exception e) {
       // unable to get a canonical form, so leave this model alone
@@ -399,7 +448,7 @@
       if (logger.isDebugEnabled()) {
         logger.debug("Finding modelTypeURI for " + modelURI);
       }
-      long rawModel = databaseSession.getSystemResolver().localize(new URIReferenceImpl(modelURI));
+      long rawModel = systemResolver.localize(new URIReferenceImpl(modelURI));
       long canModel = getCanonicalModel(rawModel);
 
       URI modelTypeURI = findModelTypeURI(canModel);
@@ -417,66 +466,10 @@
   }
 
   //
-  // Methods used only by DatabaseSession
-  //
-
-  /**
-   * Remove all the cached entries resulting from calls to
-   * {@link #findModelTypeURI}.
-   *
-   * This needs to be called at the end of transactions by
-   * {@link DatabaseSession} because the system model may change thereafter.
-   */
-  void clearSystemModelCache()
-  {
-    systemModelCacheMap.clear();
-  }
-
-  //
   // Internal methods
   //
 
   /**
-   * @param resolver  a resolver to enlist into the current transaction
-   * @throws QueryException  if the <var>resolver</var> can't be enlisted
-   */
-  Resolver enlistResolver(Resolver resolver) throws QueryException
-  {
-    // Obtain the transaction over the current thread
-    Transaction transaction;
-    try {
-      transaction = transactionManager.getTransaction();
-    }
-    catch (Exception e) {
-      throw new QueryException("Unable to obtain transaction", e);
-    }
-    if (transaction == null) {
-      if (databaseSession.getTransaction() != null) {
-        logger.error("Transaction suspended and not resumed when enlisting resolver");
-      }
-      else {
-        logger.error("Not in Transaction when enlisting resolver");
-      }
-      throw new QueryException("Failed to find transaction when enlisting resolver");
-    }
-
-    // Enlist the resolver into the transaction
-    XAResource xaResource = resolver.getXAResource();
-    if (logger.isDebugEnabled()) {
-      logger.debug("Enlisting " + resolver);
-    }
-
-    try {
-      transaction.enlistResource(xaResource);
-    }
-    catch (Exception e) {
-      throw new QueryException("Unable to enlist " + resolver + " into transaction", e);
-    }
-
-    return resolver;
-  }
-
-  /**
    * Find the type of a model.
    *
    * @param model  the local node of a model
@@ -507,7 +500,7 @@
                          new LocalNode(metadata.getRdfTypeNode()),
                          modelTypeVariable,
                          new LocalNode(metadata.getSystemModelNode()));
-    Resolution resolution = databaseSession.getSystemResolver().resolve(modelConstraint);
+    Resolution resolution = systemResolver.resolve(modelConstraint);
     assert resolution != null;
 
     // Check the solution and extract the model type (if any) from it
@@ -521,25 +514,22 @@
           throw new QueryException("Model " + model +
               " has more than one type!");
         }
-        Node modelNode = databaseSession.getSystemResolver().globalize(modelType);
+        Node modelNode = systemResolver.globalize(modelType);
         assert modelNode instanceof URIReferenceImpl;
         modelTypeURI = ((URIReferenceImpl) modelNode).getURI();
         systemModelCacheMap.put(modelLocalNode, modelTypeURI);
+
         return modelTypeURI;
-      }
-      else {
+      } else {
         return null;
       }
-    }
-    catch (TuplesException e) {
+    } catch (TuplesException e) {
       throw new QueryException("Unable to determine model type of " + model, e);
-    }
-    finally {
+    } finally {
       if ( resolution != null ) {
         try {
           resolution.close();
-        }
-        catch (TuplesException e) {
+        } catch (TuplesException e) {
           logger.warn("Unable to close find model type resolution to model " + model, e);
         }
       }
@@ -552,11 +542,11 @@
    * @throws QueryException if the <var>node</var> can't be globalized or
    *   isn't a URI reference
    */
-  /*private*/ String findProtocol(long n) throws QueryException
+  private String findProtocol(long n) throws QueryException
   {
     try {
       // Globalize the node
-      Node node = (Node) databaseSession.getSystemResolver().globalize(n);
+      Node node = (Node) systemResolver.globalize(n);
       if (!(node instanceof URIReference)) {
         throw new QueryException(node + " is not a URI reference");
       }
@@ -565,8 +555,7 @@
       }
       // Return the protocol
       return ((URIReference) node).getURI().getScheme();
-    }
-    catch (GlobalizeException e) {
+    } catch (GlobalizeException e) {
       throw new QueryException("Unable to globalize node " + n, e);
     }
   }
@@ -602,11 +591,9 @@
       return null;
     }
     // check the various names against known aliases
-    if (
-        metadata.getHostnameAliases().contains(addr.getHostName()) ||
+    if (metadata.getHostnameAliases().contains(addr.getHostName()) ||
         metadata.getHostnameAliases().contains(addr.getCanonicalHostName()) ||
-        metadata.getHostnameAliases().contains(addr.getHostAddress())
-    ) {
+        metadata.getHostnameAliases().contains(addr.getHostAddress())) {
       // change the host name to one that is recognised
       return getLocalURI(modelURI);
     }
@@ -630,6 +617,7 @@
     try {
       URI newModelURI = new URI(uri.getScheme(), newHost, uri.getPath(), uri.getFragment());
       logger.debug("Changing model URI from " + uri + " to " + newModelURI);
+
       return new URIReferenceImpl(newModelURI);
     } catch (URISyntaxException e) {
       throw new QueryException("Internal error.  Model URI cannot be manipulated.");
@@ -661,8 +649,6 @@
       throw new IllegalArgumentException("Null \"constraint\" parameter");
     }
 
-    SystemResolver systemResolver = databaseSession.getSystemResolver();
-
     ConstraintElement modelElem = constraint.getModel();
     if (modelElem instanceof Variable) {
       return resolveVariableModel(constraint);
@@ -701,8 +687,7 @@
       }
 
       // Evaluate the constraint
-      Tuples result = obtainResolver(
-          findModelResolverFactory(realModel), systemResolver).resolve(constraint);
+      Tuples result = obtainResolver(findModelResolverFactory(realModel)).resolve(constraint);
       assert result != null;
 
       return result;
@@ -731,8 +716,6 @@
     assert constraint != null;
     assert constraint.getElement(3) instanceof Variable;
 
-    SystemResolver systemResolver = databaseSession.getSystemResolver();
-
     Tuples tuples = TuplesOperations.empty();
 
     // This is the alternate code we'd use if we were to consult external
@@ -747,7 +730,7 @@
       assert resolverFactory != null;
 
       // Resolve the constraint
-      Resolver resolver = obtainResolver(resolverFactory, systemResolver);
+      Resolver resolver = obtainResolver(resolverFactory);
       if (logger.isDebugEnabled()) {
         logger.debug("Resolving " + constraint + " against " + resolver);
       }
@@ -786,74 +769,7 @@
     return tuples;
   }
 
-  public Answer innerQuery(Query query) throws QueryException {
-    // Validate "query" parameter
-    if (query == null) {
-      throw new IllegalArgumentException("Null \"query\" parameter");
-    }
-
-    if (logger.isInfoEnabled()) {
-      logger.info("Query: " + query);
-    }
-
-    boolean resumed = databaseSession.ensureTransactionResumed();
-    SystemResolver systemResolver = databaseSession.getSystemResolver();
-
-    Answer result = null;
-    try {
-      result = doQuery(systemResolver, query);
-    } catch (Throwable th) {
-      try {
-        logger.warn("Inner Query failed", th);
-        databaseSession.rollbackTransactionalBlock(th);
-      } finally {
-        databaseSession.endPreviousQueryTransaction();
-        logger.error("Inner Query should have thrown exception", th);
-        throw new IllegalStateException(
-            "Inner Query should have thrown exception");
-      }
-    }
-
-    try {
-      if (resumed) {
-        databaseSession.suspendTransactionalBlock();
-      }
-
-      return result;
-    } catch (Throwable th) {
-      databaseSession.endPreviousQueryTransaction();
-      logger.error("Failed to suspend Transaction", th);
-      throw new QueryException("Failed to suspend Transaction");
-    }
-  }
-
-  public void registerAnswer(SubqueryAnswer answer) {
-    if (logger.isDebugEnabled()) {
-      logger.debug("registering Answer: " + System.identityHashCode(answer));
-    }
-    outstandingAnswers.add(answer);
-  }
-
-  public void deregisterAnswer(SubqueryAnswer answer) throws QueryException {
-    if (logger.isDebugEnabled()) {
-      logger.debug("deregistering Answer: " + System.identityHashCode(answer));
-    }
-
-    if (!outstandingAnswers.contains(answer)) {
-      logger.info("Stale answer being closed");
-    } else {
-      outstandingAnswers.remove(answer);
-      if (databaseSession.autoCommit && outstandingAnswers.isEmpty()) {
-        if (databaseSession.getTransaction() != null) {
-          databaseSession.resumeTransactionalBlock();
-        }
-        databaseSession.endTransactionalBlock("Could not commit query");
-      }
-    }
-  }
-
   Tuples innerCount(LocalQuery localQuery) throws QueryException {
-    // Validate "query" parameter
     if (localQuery == null) {
       throw new IllegalArgumentException("Null \"query\" parameter");
     }
@@ -861,43 +777,21 @@
     if (logger.isInfoEnabled()) {
       logger.info("Inner Count: " + localQuery);
     }
-
-    boolean resumed = databaseSession.ensureTransactionResumed();
-    SystemResolver systemResolver = databaseSession.getSystemResolver();
-
-    Tuples result = null;
     try {
       LocalQuery lq = (LocalQuery)localQuery.clone();
       transform(lq);
-      result = lq.resolve();
+      Tuples result = lq.resolve();
       lq.close();
-    } catch (Throwable th) {
-      try {
-        logger.warn("Inner Query failed", th);
-        databaseSession.rollbackTransactionalBlock(th);
-      } finally {
-        databaseSession.endPreviousQueryTransaction();
-        logger.error("Inner Query should have thrown exception", th);
-        throw new IllegalStateException(
-            "Inner Query should have thrown exception");
-      }
-    }
 
-    try {
-      if (resumed) {
-        databaseSession.suspendTransactionalBlock();
-      }
-
       return result;
-    } catch (Throwable th) {
-      databaseSession.endPreviousQueryTransaction();
-      logger.error("Failed to suspend Transaction", th);
-      throw new QueryException("Failed to suspend Transaction");
+    } catch (QueryException eq) {
+      throw eq;
+    } catch (Exception e) {
+      throw new QueryException("Failed to evaluate count", e);
     }
   }
 
-  protected void doModify(SystemResolver systemResolver, URI modelURI,
-      Statements statements, boolean insert) throws Throwable {
+  protected void doModify(URI modelURI, Statements statements, boolean insert) throws Throwable {
     long model = systemResolver.localize(new URIReferenceImpl(modelURI));
     model = getCanonicalModel(model);
 
@@ -917,7 +811,7 @@
     }
 
     // Obtain a resolver for the destination model type
-    Resolver resolver = obtainResolver(findModelResolverFactory(model), systemResolver);
+    Resolver resolver = obtainResolver(findModelResolverFactory(model));
     assert resolver != null;
 
     if (logger.isDebugEnabled()) {
@@ -931,7 +825,7 @@
     }
   }
 
-  public Answer doQuery(SystemResolver systemResolver, Query query) throws Exception
+  public Answer doQuery(Query query) throws Exception
   {
     Answer result;
 
@@ -941,13 +835,11 @@
 
     // Complete the numerical phase of resolution
     Tuples tuples = localQuery.resolve();
-    result = new SubqueryAnswer(this, systemResolver, tuples, query.getVariableList());
+    result = new TransactionalAnswer(transaction, new SubqueryAnswer(this, systemResolver, tuples, query.getVariableList()));
+    answers.put(result, null);
     tuples.close();
     localQuery.close();
 
-    if (logger.isDebugEnabled()) {
-      logger.debug("Answer rows = " + result.getRowCount());
-    }
     return result;
   }
 
@@ -984,4 +876,95 @@
     mutableLocalQueryImpl.close();
   }
 
+  void clear() throws QueryException {
+    try {
+      Iterator i = answers.keySet().iterator();
+      while (i.hasNext()) {
+        ((TransactionalAnswer)i.next()).sessionClose();
+      }
+      answers.clear();
+    } catch (TuplesException et) {
+      throw new QueryException("Error force-closing answers", et);
+    }
+
+    clearCache();
+    systemResolver = null;
+    systemModelCacheMap.clear();
+    enlistedResolverMap.clear();
+  }
+
+  public SystemResolver getSystemResolver() {
+    return systemResolver;
+  }
+
+
+  /**
+   * Clear the cache of temporary models.
+   */
+  private void clearCache() {
+    // Clear the temporary models
+    if (!cachedModelSet.isEmpty()) {
+      try {
+        Resolver temporaryResolver =
+          temporaryResolverFactory.newResolver(true, systemResolver, systemResolver);
+        for (Iterator i = cachedModelSet.iterator(); i.hasNext();) {
+          LocalNode modelLocalNode = (LocalNode) i.next();
+          long model = modelLocalNode.getValue();
+
+          if (changedCachedModelSet.contains(modelLocalNode)) {
+            // Write back the modifications to the original model
+            try {
+              Resolver resolver =
+                findResolverFactory(model).newResolver(true, systemResolver, systemResolver);
+              Variable s = new Variable("s");
+              Variable p = new Variable("p");
+              Variable o = new Variable("o");
+              resolver.modifyModel(model,
+                new TuplesWrapperStatements(temporaryResolver.resolve(
+                    new ConstraintImpl(s, p, o, modelLocalNode)), s, p, o),
+                true  // insert the content
+              );
+            } catch (Exception e) {
+              logger.error("Failed to write back cached model " + model +
+                           " after transaction", e);
+            }
+            changedCachedModelSet.remove(modelLocalNode);
+          }
+
+          // Remove the cached model
+          try {
+            temporaryResolver.removeModel(model);
+          } catch (Exception e) {
+            logger.error("Failed to clear cached model " + model + " after transaction", e);
+          }
+          i.remove();
+        }
+      } catch (Exception e) {
+        logger.error("Failed to clear cached models after transaction", e);
+      }
+    }
+  }
+
+  public void abort() {
+    Iterator i = enlistedResolverMap.values().iterator();
+    while (i.hasNext()) {
+      ((Resolver)i.next()).abort();
+    }
+    try {
+      this.clear();
+    } catch (QueryException eq) {
+      throw new IllegalStateException("Error aborting OperationContext", eq);
+    }
+  }
+
+  public void initiate(MulgaraTransaction transaction) throws QueryException {
+    try {
+      this.transaction = transaction;
+      this.systemResolver = systemResolverFactory.newResolver(isWriting);
+      transaction.enlist(systemResolver);
+    } catch (Exception e) {
+      throw new QueryException("Unable to enlist systemResolver:" + 
+          systemResolver + " into transaction", e);
+    }
+  }
 }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -39,9 +39,6 @@
 // Java 2 enterprise packages
 import javax.transaction.RollbackException;
 import javax.transaction.Status;
-import javax.transaction.SystemException;
-import javax.transaction.Transaction;
-import javax.transaction.TransactionManager;
 
 // Third party packages
 import org.apache.log4j.Logger;
@@ -78,7 +75,7 @@
  *
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
-class DatabaseSession implements Session, LocalSession {
+class DatabaseSession implements Session {
   public static final boolean ASSERT_STATEMENTS = true;
   public static final boolean DENY_STATEMENTS = false;
 
@@ -86,37 +83,14 @@
   private static final Logger logger =
     Logger.getLogger(DatabaseSession.class.getName());
 
-  private static DatabaseSession writeSession = null;
-  private boolean writing;
-
   /**
-   * The models from external resolvers which have been cached as temporary
-   * models.
-   *
-   * Every model in this set can be manipulated by resolvers from the
-   * {@link #temporaryResolverFactory}.
-   */
-  private final Set cachedModelSet = new HashSet();
-
-  /**
    * Resolver factories that should be have access to their models cached.
    *
    * This field is read-only.
    */
   private final Set cachedResolverFactorySet;
 
-  /**
-   * The models from external resolvers which have been cached as temporary
-   * models and modified.
-   *
-   * Every model in this set can be manipulated by resolvers from the
-   * {@link #temporaryResolverFactory}.
-   */
-  private final Set changedCachedModelSet = new HashSet();
-
-  /**
-   * The list of all registered {@link ResolverFactory} instances.
-   */
+  /** The list of all registered {@link ResolverFactory} instances.  */
   private final List resolverFactoryList;
 
   /**
@@ -133,8 +107,6 @@
 
   private final DatabaseMetadata metadata;
 
-  private final DatabaseOperationContext operationContext;
-
   /** Security adapters this instance should enforce. */
   private final List securityAdapterList;
 
@@ -147,66 +119,23 @@
   /** Factory used to obtain the SystemResolver */
   private final SystemResolverFactory systemResolverFactory;
 
-  /** Resolver used for accessing the system model (<code>#</code>).  */
-  protected SystemResolver systemResolver;
-
   /** Factory used to obtain the SystemResolver */
   private final ResolverFactory temporaryResolverFactory;
 
   /** Source of transactions.  */
-  private final TransactionManager transactionManager;
+  private final MulgaraTransactionManager transactionManager;
 
-  /** Session transaction */
-  private Transaction transaction;
-
   /** The name of the rule loader to use */
   private String ruleLoaderClassName;
 
   /** A fallback rule loader */
   private static final String DUMMY_RULE_LOADER = "org.mulgara.rules.DummyRuleLoader";
 
-  private int opState;
-  private static final int UNINIT = 0;
-  private static final int BEGIN = 1;
-  private static final int RESUME = 2;
-  private static final int FINISH = 3;
-  private static final String[] opStates = {
-      "UNINIT", "BEGIN", "RESUME", "FINISH", };
-
-  private final Map enlistedResolverMap;
-
-  private Set outstandingAnswers;
-
-  /**
-   * Whether each method call of the {@link Session} interface should
-   * implicitly have a transaction created for it and be performed within that
-   * transaction.
-   *
-   * This defaults to <code>true</code> until modified by the
-   * {@link #setAutoCommit} method.
-   */
-  boolean autoCommit = true;
-  private boolean inFailedTransaction = false;
-
-  /**
-   * If a transaction is marked for rollback by the
-   * {@link #rollbackTransactionalBlock} method, this field holds the exception
-   * that caused the rollback so that the {@link #endTransactionalBlock} method
-   * can add it as the cause of the {@link RollbackException} it will
-   * subsequently throw.
-   */
-  private Throwable rollbackCause = null;
-
-  private boolean explicitRollback = false;
-
-  /**
-   * The registered {@link ContentHandler} instances.
-   */
+  /** The registered {@link ContentHandler} instances.  */
   private ContentHandlerManager contentHandlers;
 
-  //
-  // Constructor
-  //
+  /** The temporary model type-URI. */
+  private final URI temporaryModelTypeURI;
 
   /**
    * Construct a database session.
@@ -241,7 +170,7 @@
    *   external models
    * @throws IllegalArgumentException if any argument is <code>null</code>
    */
-  DatabaseSession(TransactionManager transactionManager,
+  DatabaseSession(MulgaraTransactionManager transactionManager,
       List securityAdapterList,
       List symbolicTransformationList,
       ResolverSessionFactory resolverSessionFactory,
@@ -264,43 +193,35 @@
 
     // Validate parameters
     if (transactionManager == null) {
-      throw new IllegalArgumentException("Null \"transactionManager\" parameter");
+      throw new IllegalArgumentException("Null 'transactionManager' parameter");
     } else if (securityAdapterList == null) {
-      throw new IllegalArgumentException("Null \"securityAdapterList\" parameter");
+      throw new IllegalArgumentException("Null 'securityAdapterList' parameter");
     } else if (symbolicTransformationList == null) {
-      throw new IllegalArgumentException("Null \"symbolicTransformationList\" parameter");
+      throw new IllegalArgumentException("Null 'symbolicTransformationList' parameter");
     } else if (resolverSessionFactory == null) {
-      throw new IllegalArgumentException("Null \"resolverSessionFactory\" parameter");
+      throw new IllegalArgumentException("Null 'resolverSessionFactory' parameter");
     } else if (systemResolverFactory == null) {
-      throw new IllegalArgumentException("Null \"systemResolverFactory\" parameter");
+      throw new IllegalArgumentException("Null 'systemResolverFactory' parameter");
     } else if (temporaryResolverFactory == null) {
-      throw new IllegalArgumentException("Null \"temporaryResolverFactory\" parameter");
+      throw new IllegalArgumentException("Null 'temporaryResolverFactory' parameter");
     } else if (resolverFactoryList == null) {
-      throw new IllegalArgumentException("Null \"resolverFactoryList\" parameter");
+      throw new IllegalArgumentException("Null 'resolverFactoryList' parameter");
     } else if (externalResolverFactoryMap == null) {
-      throw new IllegalArgumentException("Null \"externalResolverFactoryMap\" parameter");
+      throw new IllegalArgumentException("Null 'externalResolverFactoryMap' parameter");
     } else if (internalResolverFactoryMap == null) {
-      throw new IllegalArgumentException("Null \"internalResolverFactoryMap\" parameter");
+      throw new IllegalArgumentException("Null 'internalResolverFactoryMap' parameter");
     } else if (contentHandlers == null) {
-      throw new IllegalArgumentException("Null \"contentHandlers\" parameter");
+      throw new IllegalArgumentException("Null 'contentHandlers' parameter");
     } else if (metadata == null) {
-      throw new IllegalArgumentException("Null \"metadata\" parameter");
+      throw new IllegalArgumentException("Null 'metadata' parameter");
+    } else if (cachedResolverFactorySet == null) {
+      throw new IllegalArgumentException("Null 'cachedResolverFactorySet' parameter");
+    } else if (temporaryModelTypeURI == null) {
+      throw new IllegalArgumentException("Null 'temporaryModelTypeURI' parameter");
     } else if (ruleLoaderClassName == null) {
       ruleLoaderClassName = DUMMY_RULE_LOADER;
     }
 
-    if (cachedResolverFactorySet == null) {
-      throw new IllegalArgumentException(
-        "Null \"cachedResolverFactorySet\" parameter"
-      );
-    }
-
-    if (temporaryModelTypeURI == null) {
-      throw new IllegalArgumentException(
-        "Null \"temporaryModelTypeURI\" parameter"
-      );
-    }
-
     // Initialize fields
     this.transactionManager = transactionManager;
     this.securityAdapterList = securityAdapterList;
@@ -314,46 +235,22 @@
     this.metadata                   = metadata;
     this.contentHandlers            = contentHandlers;
     this.cachedResolverFactorySet   = cachedResolverFactorySet;
+    this.temporaryModelTypeURI      = temporaryModelTypeURI;
     this.ruleLoaderClassName        = ruleLoaderClassName;
 
-    this.outstandingAnswers         = new HashSet();
-    this.transaction                = null;
-    this.enlistedResolverMap        = new HashMap();
-    this.opState                    = FINISH;
-    this.operationContext           = new DatabaseOperationContext(
-                                        cachedModelSet,
-                                        cachedResolverFactorySet,
-                                        changedCachedModelSet,
-                                        this,
-                                        enlistedResolverMap,
-                                        externalResolverFactoryMap,
-                                        internalResolverFactoryMap,
-                                        metadata,
-                                        securityAdapterList,
-                                        temporaryModelTypeURI,
-                                        temporaryResolverFactory,
-                                        transactionManager,
-                                        outstandingAnswers,
-                                        symbolicTransformationList
-                                      );
-
     if (logger.isDebugEnabled()) {
       logger.debug("Constructed DatabaseSession");
     }
 
     // Set the transaction timeout to an hour
-    try {
-      transactionManager.setTransactionTimeout(3600);
-    } catch (SystemException e) {
-      logger.warn("Unable to set transaction timeout to 3600s", e);
-    }
+    transactionManager.setTransactionTimeout(3600);
   }
 
 
   /**
    * Non-rule version of the constructor.  Accepts all parameters except ruleLoaderClassName.
    */
-  DatabaseSession(TransactionManager transactionManager,
+  DatabaseSession(MulgaraTransactionManager transactionManager,
       List securityAdapterList,
       List symbolicTransformationList,
       ResolverSessionFactory resolverSessionFactory,
@@ -379,68 +276,15 @@
   /**
    * Used by Database *only* to bootstrap the system model on DB startup.
    */
-  long bootstrapSystemModel(DatabaseMetadataImpl metadata) throws
-      QueryException {
+  long bootstrapSystemModel(DatabaseMetadataImpl metadata) throws QueryException {
     logger.info("Bootstrapping System Model");
-    // Validate parameters
-    if (metadata == null) {
-      throw new IllegalArgumentException("metadata null");
-    }
 
-    // Create the model
-    systemResolver = beginTransactionalBlock(true);
-    try {
-      // Find the local node identifying the model
-      long model = systemResolver.localizePersistent(
-          new URIReferenceImpl(metadata.getSystemModelURI()));
-      long rdfType = systemResolver.localizePersistent(
-          new URIReferenceImpl(metadata.getRdfTypeURI()));
-      long modelType = systemResolver.localizePersistent(
-          new URIReferenceImpl(metadata.getSystemModelTypeURI()));
+    BootstrapOperation operation = new BootstrapOperation(metadata);
+    execute(operation, "Failed to bootstrap system-model");
 
-      // Use the session to create the model
-      systemResolver.modifyModel(model, new SingletonStatements(model, rdfType,
-          modelType), true);
-      metadata.initializeSystemNodes(model, rdfType, modelType);
+    systemResolverFactory.setDatabaseMetadata(metadata);
 
-      long preSubject = systemResolver.localizePersistent(
-          new URIReferenceImpl(metadata.getPreallocationSubjectURI()));
-      long prePredicate = systemResolver.localizePersistent(
-          new URIReferenceImpl(metadata.getPreallocationPredicateURI()));
-      long preModel = systemResolver.localizePersistent(
-          new URIReferenceImpl(metadata.getPreallocationModelURI()));
-
-      // Every node cached by DatabaseMetadata must be preallocated
-      systemResolver.modifyModel(preModel,
-          new SingletonStatements(preSubject, prePredicate, model),
-          true);
-      systemResolver.modifyModel(preModel,
-          new SingletonStatements(preSubject, prePredicate, rdfType),
-          true);
-      systemResolver.modifyModel(preModel,
-          new SingletonStatements(preSubject, prePredicate, modelType),
-          true);
-      systemResolver.modifyModel(preModel,
-          new SingletonStatements(preSubject, prePredicate, preSubject),
-          true);
-      systemResolver.modifyModel(preModel,
-          new SingletonStatements(preSubject, prePredicate, prePredicate),
-          true);
-      systemResolver.modifyModel(preModel,
-          new SingletonStatements(preSubject, prePredicate, preModel),
-          true);
-
-      metadata.initializePreallocationNodes(preSubject, prePredicate, preModel);
-
-      systemResolverFactory.setDatabaseMetadata(metadata);
-
-      return model;
-    } catch (Throwable e) {
-      rollbackTransactionalBlock(e);
-      return -1; // Should be discarded by exception in endTransactionalBlock.
-    } finally {
-      endTransactionalBlock("Could not commit system model bootstrap");
-    }
+    return operation.getResult();
   }
 
   /**
@@ -540,14 +384,12 @@
       logger.info("QUERY: " + query);
     }
 
-    // Evaluate the query
     QueryOperation queryOperation = new QueryOperation(query, this);
-    executeQuery(queryOperation);
+    execute(queryOperation, "Query failed");
     return queryOperation.getAnswer();
   }
 
   public List query(List queryList) throws QueryException {
-
     if (logger.isInfoEnabled()) {
       StringBuffer log = new StringBuffer("QUERYING LIST: ");
       for (int i = 0; i < queryList.size(); i++) {
@@ -557,7 +399,7 @@
     }
 
     QueryOperation queryOperation = new QueryOperation(queryList, this);
-    executeQuery(queryOperation);
+    execute(queryOperation, "Failed list query");
     return queryOperation.getAnswerList();
   }
 
@@ -576,9 +418,8 @@
     if (logger.isInfoEnabled()) {
       logger.info("REMOVE MODEL: " + modelURI);
     }
-    // Validate "modelURI" parameter
     if (modelURI == null) {
-      throw new IllegalArgumentException("Null \"modelURI\" parameter");
+      throw new IllegalArgumentException("Null 'modelURI' parameter");
     }
 
     execute(new RemoveModelOperation(modelURI), "Unable to remove " + modelURI);
@@ -624,8 +465,7 @@
     // Validate parameters
     if (destinationModelURI == null) {
       throw new IllegalArgumentException("Null 'destinationModelURI' parameter");
-    }
-    if (modelExpression == null) {
+    } else if (modelExpression == null) {
       throw new IllegalArgumentException("Null 'modelExpression' parameter");
     }
 
@@ -680,99 +520,39 @@
 
   public void setAutoCommit(boolean autoCommit) throws QueryException {
     if (logger.isInfoEnabled()) {
-      logger.info("setAutoCommit(" + autoCommit + ") called with autoCommit = " + this.autoCommit);
+      logger.info("setAutoCommit(" + autoCommit + ") called.");
     }
-
-    if (!this.autoCommit && autoCommit) { // Turning autoCommit on
-      try {
-        resumeTransactionalBlock();
-      } finally {
-        this.autoCommit = true;
-        this.inFailedTransaction = false;
-        endTransactionalBlock("Extended transaction failed");
-      }
-    } else if (this.autoCommit && !autoCommit) { // Turning autoCommit off
-      if (this.transaction != null) {
-        resumeTransactionalBlock();
-        endPreviousQueryTransaction();
-      }
-      systemResolver = beginTransactionalBlock(true);
-      try {
-        suspendTransactionalBlock();
-      } catch (Throwable th) {
-        logger.error("Failed to suspend transaction", th);
-        rollbackTransactionalBlock(th);
-        endTransactionalBlock("Could set auto Commit off");
-      }
-      this.autoCommit = false;
-    } else if (this.inFailedTransaction) { // Reset after failed autoCommit off based transaction.
-      this.inFailedTransaction = false;
-    } else { // Leaving autoCommit the same
-      if (logger.isInfoEnabled()) {
-        logger.info("Invalid call to setAutoCommit(" + autoCommit + ") called with autoCommit = " + this.autoCommit);
-      }
+    try {
+      transactionManager.setAutoCommit(this, autoCommit);
+    } catch (MulgaraTransactionException em) {
+      throw new QueryException("Error setting autocommit", em);
     }
   }
 
   public void commit() throws QueryException {
     logger.info("Committing transaction");
-    if (!autoCommit) {
-      synchronized (DatabaseSession.class) {
-        setAutoCommit(true);
-        setAutoCommit(false);
-      }
+    try {
+      transactionManager.commit(this);
+    } catch (MulgaraTransactionException em) {
+      throw new QueryException("Error performing commit", em);
     }
   }
 
   public void rollback() throws QueryException {
     logger.info("Rollback transaction");
-    if (autoCommit) {
-      throw new QueryException(
-          "Attempt to rollback transaction outside transaction");
-    }
-    resumeTransactionalBlock();
     try {
-      explicitRollback = true;
-      rollbackTransactionalBlock(new QueryException(
-          "Explicit rollback requested on session"));
-    } catch (Throwable th) {
-      logger.error("Failed to rollback transaction", th);
-      throw new QueryException("Rollback failed", th);
-    } finally {
-      synchronized (DatabaseSession.class) {
-        try {
-          endTransactionalBlock("Rollback failed, ending transaction");
-        } catch (Throwable th) {
-          throw new QueryException("Rollback failed", th);
-        }
-        setAutoCommit(false);
-      }
+      transactionManager.rollback(this);
+    } catch (MulgaraTransactionException em) {
+      throw new QueryException("Error performing rollback", em);
     }
   }
 
   public void close() throws QueryException {
     logger.info("Closing session");
-    if (!autoCommit) {
-      logger.warn("Closing session while holding write-lock");
-
-      try {
-        resumeTransactionalBlock();
-      } catch (Throwable th) {
-        releaseWriteLock();
-        throw new QueryException("Error while resuming transaction in close", th);
-      }
-
-      try {
-        rollbackTransactionalBlock(
-            new QueryException("Attempt to close session whilst in transaction"));
-      } finally {
-        endTransactionalBlock("Failed to release write-lock in close");
-      }
-    } else {
-      if (this.transaction != null) {
-        resumeTransactionalBlock();
-        endPreviousQueryTransaction();
-      }
+    try {
+      transactionManager.terminateCurrentTransactions(this);
+    } catch (MulgaraTransactionException em) {
+      throw new QueryException("Error closing session. Forced close required", em);
     }
   }
 
@@ -785,10 +565,6 @@
       logger.debug("Login of " + user + " to " + securityDomain);
     }
 
-    /*
-    execute(new LoginOperation(securityDomain, user, password),
-            "Unable to login " + user + " to " + securityDomain);
-    */
     if (securityDomain.equals(metadata.getSecurityDomainURI())) {
       // Propagate the login event to the security adapters
       for (Iterator i = securityAdapterList.iterator(); i.hasNext();) {
@@ -797,482 +573,7 @@
     }
   }
 
-  //
-  // Transaction control methods.  Implements LocalSession.
-  //
-
   /**
-   * Start's or resumes a transaction for an operation.
-   *
-   * Using start/finish TransactionalOperation ensures properly matched pairs of
-   * begin/end and suspend/resume.
-   */
-  public void startTransactionalOperation(boolean needsWrite) throws
-      QueryException {
-    logger.info("Starting Transactional Operation");
-    if (opState != FINISH) {
-      throw new IllegalArgumentException(
-          "Attempt to start transactional operation during: " +
-          opStates[opState]);
-    }
-    if (autoCommit) {
-      if (this.transaction != null) {
-        resumeTransactionalBlock();
-        endPreviousQueryTransaction();
-      }
-      beginTransactionalBlock(needsWrite);
-      logger.info("BEGIN new transaction.");
-      opState = BEGIN;
-    } else {
-      resumeTransactionalBlock();
-      logger.info("RESUME old transaction.");
-      opState = RESUME;
-    }
-  }
-
-  /**
-   * Mark the current transaction for rollback due to an exception.
-   *
-   * This records the exception which caused the rollback in the
-   * {@link #rollbackCause} field.
-   */
-  public void rollbackTransactionalBlock(Throwable throwable) throws
-      QueryException {
-    logger.info("Rollback Transactional Block");
-    assert throwable != null;
-
-    try {
-      if (logger.isDebugEnabled()) {
-        logger.debug("Marking transaction for rollback", throwable);
-      }
-      transactionManager.setRollbackOnly();
-    } catch (Throwable e) {
-      logger.error("Needed to mark transaction for rollback", throwable);
-      logger.error("Unable to mark transaction for rollback", e);
-      throw new QueryException("Unable to mark transaction for rollback", e);
-    }
-
-    rollbackCause = throwable;
-  }
-
-  /**
-   * Ends's or suspends a transaction for an operation.
-   *
-   * Using start/finish TransactionalOperation ensures properly matched pairs of
-   * begin/end and suspend/resume.
-   */
-  public void finishTransactionalOperation(String errorString) throws
-      QueryException {
-    logger.info("Finishing Transactional Operation");
-    if (logger.isDebugEnabled()) {
-      logger.debug("opState = " + opStates[opState]);
-      logger.debug("autoCommit = " + autoCommit);
-    }
-    if (opState == FINISH) {
-      throw new IllegalArgumentException(
-          "Attempt to finish transactional operation during: " + opStates[opState]);
-    }
-    if (autoCommit) {
-      try {
-        endTransactionalBlock(errorString);
-      } finally {
-        logger.info("FINISH(end) implicit transaction.");
-        opState = FINISH;
-      }
-    } else {
-      try {
-        suspendTransactionalBlock();
-      } catch (Throwable th) {
-        logger.error("Failed to suspend transaction", th);
-        try {
-          rollbackTransactionalBlock(new QueryException("Failed to suspend transaction"));
-        } finally {
-          endTransactionalBlock("Failed to suspend transaction at end of operation");
-        }
-      } finally {
-        logger.info("FINISH(suspend) explicit transaction.");
-        opState = FINISH;
-      }
-    }
-  }
-
-  /**
-   * Resumes the previously suspended transaction from the current session.
-   *
-   * @throws QueryException Must be called outside the try/catch(Throwable) block
-   * protecting the transaction.
-   */
-  public void resumeTransactionalBlock() throws QueryException {
-    logger.info("Resume Transactional Block");
-    if (transaction == null) {
-      throw new IllegalStateException("Attempt to resume unsuspended transaction");
-    } else if (inFailedTransaction == true) {
-      throw new IllegalStateException("Transaction already failed, set autocommit true to reset");
-    }
-
-    try {
-      transactionManager.resume(this.transaction);
-      this.transaction = null;
-    } catch (Exception e) {
-      logger.error("Resume failed", e);
-      throw new QueryException("Failed to resume transaction", e);
-    }
-  }
-
-  /**
-   * Suspends current transaction, storing it in session for latter resumption.
-   *
-   * @throws Throwable Must be called inside the try/catch(Throwable) block
-   * protecting the transaction.
-   */
-  public void suspendTransactionalBlock() throws Throwable {
-    logger.info("Suspend Transactional Block");
-    if (transaction != null) {
-      throw new IllegalStateException(
-          "Attempt to suspend unresumed transaction.");
-    }
-    if (logger.isInfoEnabled()) {
-      logger.info(
-         "Suspend Transactional Block autocommit=" + autoCommit +
-         " transaction status=" + StatusFormat.formatStatus(transactionManager)
-      );
-    }
-
-    int status = transactionManager.getStatus();
-    if (!autoCommit &&
-        (status == Status.STATUS_MARKED_ROLLBACK ||
-         status == Status.STATUS_ROLLEDBACK ||
-         status == Status.STATUS_ROLLING_BACK)) {
-      inFailedTransaction = true;
-      throw new QueryException("Transaction marked for rollback");
-    }
-
-    this.transaction = transactionManager.suspend();
-  }
-
-  public ResolverSession getResolverSession() {
-    return systemResolver;
-  }
-
-  //
-  // Internal Transactional Methods.  Not exposed via the interface.
-  //
-
-  /**
-   * Mark the beginning of a transactional block.
-   *
-   * This begins a transaction if {@link #autoCommit} is <code>true</code>.
-   *
-   * @throws QueryException if a transaction needed to be begun and couldn't be
-   */
-  private SystemResolver beginTransactionalBlock(boolean allowWrites) throws
-      QueryException {
-    if (logger.isInfoEnabled()) {
-      logger.info("Beginning transactional block: autocommit = " + autoCommit);
-    }
-
-    // Start the transaction
-    if (inFailedTransaction == true) {
-      throw new IllegalStateException("Transaction already failed, set autocommit true to reset");
-    } else if (!enlistedResolverMap.isEmpty()) {
-      throw new QueryException("Stale resolvers found in enlistedResolverMap");
-    }
-
-
-    if (allowWrites) {
-      try {
-        obtainWriteLock();
-      } catch (InterruptedException ei) {
-        throw new QueryException("Unable to obtain write lock", ei);
-      }
-    }
-
-    try {
-      transactionManager.begin();
-      if (systemResolver != null) {
-        throw new QueryException("beginning nested transaction");
-      }
-      systemResolver = systemResolverFactory.newResolver(allowWrites);
-      return (SystemResolver) operationContext.enlistResolver(systemResolver);
-    } catch (Exception e) {
-      throw new QueryException("Unable to begin transaction", e);
-    }
-  }
-
-  /**
-   * Mark the end of a transactional block.
-   *
-   * This commits the current transaction if {@link #autoCommit} is
-   * <code>true</code>.
-   *
-   * @throws QueryException if a transaction needed to be committed and
-   *   couldn't be
-   */
-  void endTransactionalBlock(String failureMessage) throws QueryException {
-    if (logger.isInfoEnabled()) {
-      logger.info(
-        "End Transactional Block autocommit=" + autoCommit +
-        " transaction status=" + StatusFormat.formatStatus(transactionManager)
-      );
-    }
-
-    try {
-      // Commit the transaction
-      if (rollbackCause == null) {
-        transactionManager.commit();
-      } else {
-        try {
-          transactionManager.commit();
-        } catch (RollbackException e) {
-          // Sneakily reinsert the exception recorded earlier by the
-          // rollbackTransactionalBlock method.  Without this feature, it's
-          // very difficult to determine why a rollback occurred.
-          e.initCause(rollbackCause);
-          throw e;
-        } finally {
-          rollbackCause = null;
-        }
-      }
-    } catch (Exception e) {
-      if (!explicitRollback) {
-        throw new QueryException(failureMessage, e);
-      }
-    } finally {
-      releaseWriteLock();
-      enlistedResolverMap.clear();
-      outstandingAnswers.clear();
-      clearCache();
-      operationContext.clearSystemModelCache();
-
-      systemResolver = null;
-      explicitRollback = false;
-      autoCommit = true;
-    }
-  }
-
-  void endPreviousQueryTransaction() throws QueryException {
-    logger.debug("Clearing previous transaction");
-
-    // Save the exception.
-    Throwable tmpThrowable = rollbackCause;
-
-    Set answers = new HashSet(outstandingAnswers);
-    Iterator i = answers.iterator();
-    while (i.hasNext()) {
-      try {
-        SubqueryAnswer s = (SubqueryAnswer) i.next();
-        operationContext.deregisterAnswer(s);
-        //Do not close tuples - for Jena and JRDF.
-        //s.close();
-      } catch (Throwable th) {
-        logger.debug("Failed to close preexisting answer", th);
-      }
-    }
-
-    // If by closing the answer we have called endTransactionalBlock then
-    // throw the saved exception.
-    if ((rollbackCause == null) && (tmpThrowable != null) &&
-        (systemResolver == null)) {
-      throw new QueryException("Failure ending previous query", tmpThrowable);
-    }
-
-    try {
-      if (!outstandingAnswers.isEmpty()) {
-        throw new QueryException("Failed to clear preexisting transaction");
-      }
-      if (this.transaction != null) {
-        throw new QueryException("Failed to void suspended transaction");
-      }
-      if (transactionManager.getTransaction() != null) {
-        throw new QueryException("Failed to end transaction");
-      }
-    } catch (QueryException eq) {
-      endTransactionalBlock("Error ending previous query");
-      throw eq;
-    } catch (Throwable th) {
-      endTransactionalBlock("Error ending previous query");
-      throw new QueryException("Failure ending previous query", th);
-    }
-  }
-
-  private void obtainWriteLock() throws InterruptedException {
-    logger.info("Trying to obtain write lock. ");
-    synchronized (DatabaseSession.class) {
-      if (DatabaseSession.writeSession == this) {
-        return;
-      }
-      while (DatabaseSession.writeSession != null) {
-        DatabaseSession.class.wait();
-      }
-      DatabaseSession.writeSession = this;
-      this.writing = true;
-      logger.info("Obtained write lock. ");
-    }
-  }
-
-  private void releaseWriteLock() {
-    synchronized (DatabaseSession.class) {
-      if (DatabaseSession.writeSession == this) {
-        logger.info("Releasing write lock");
-        DatabaseSession.writeSession = null;
-        this.writing = false;
-        DatabaseSession.class.notifyAll();
-      }
-    }
-  }
-
-  //
-  // Internal support methods.
-  //
-
-  /**
-   * Clear the cache of temporary models.
-   */
-  private void clearCache()
-  {
-    // Clear the temporary models
-    if (!cachedModelSet.isEmpty()) {
-      try {
-        Resolver temporaryResolver =
-          temporaryResolverFactory.newResolver(true,
-                                               systemResolver,
-                                               systemResolver);
-        for (Iterator i = cachedModelSet.iterator(); i.hasNext();) {
-          LocalNode modelLocalNode = (LocalNode) i.next();
-          long model = modelLocalNode.getValue();
-
-          if (changedCachedModelSet.contains(modelLocalNode)) {
-            // Write back the modifications to the original model
-            try {
-              Resolver resolver =
-                findResolverFactory(model).newResolver(true,
-                                                       systemResolver,
-                                                       systemResolver);
-              Variable s = new Variable("s"),
-                       p = new Variable("p"),
-                       o = new Variable("o");
-              resolver.modifyModel(
-                model,
-                new TuplesWrapperStatements(
-                  temporaryResolver.resolve(
-                    new ConstraintImpl(s, p, o, modelLocalNode)
-                  ),
-                  s, p, o
-                ),
-                true  // insert the content
-              );
-            }
-            catch (Exception e) {
-              logger.error("Failed to write back cached model " + model +
-                           " after transaction", e);
-            }
-            changedCachedModelSet.remove(modelLocalNode);
-          }
-
-          // Remove the cached model
-          try {
-            temporaryResolver.removeModel(model);
-          }
-          catch (Exception e) {
-            logger.error(
-              "Failed to clear cached model " + model + " after transaction",
-               e
-            );
-          }
-          i.remove();
-        }
-      }
-      catch (Exception e) {
-        logger.error("Failed to clear cached models after transaction", e);
-      }
-    }
-  }
-
-  /**
-   * Find a cached resolver factory for write back.
-   *
-   * @return a completely unwrapped resolver factory
-   */
-  // TODO: Common code with DatabaseOperationContent.findModelResolverFactory
-  //       should be consolidated.
-  private ResolverFactory findResolverFactory(long model) throws QueryException
-  {
-    if (logger.isDebugEnabled()) {
-      logger.debug("Finding raw resolver factory for model " + model);
-    }
-
-    try {
-      // get the model URI
-      Node modelNode = systemResolver.globalize(model);
-      if (!(modelNode instanceof URIReference)) {
-        throw new QueryException(modelNode.toString() + " is not a valid Model");
-      }
-      URI modelURI = ((URIReference)modelNode).getURI();
-
-      // test the model URI against the current server
-      try {
-        if (logger.isDebugEnabled()) {
-          logger.debug("Comparing " + metadata.getURI().toString() + " to " + (new URI(modelURI.getScheme(),
-                  modelURI.getSchemeSpecificPart(), null)).toString());
-        }
-        if (metadata.getURI().equals(new URI(modelURI.getScheme(), modelURI.getSchemeSpecificPart(), null))) {
-          // should be on the current server, but was not found here
-          throw new QueryException(modelNode.toString() + " is not a Model");
-        }
-      }
-      catch (URISyntaxException use) {
-        throw new QueryException("Internal error.  Model URI cannot be manipulated.");
-      }
-
-      // This is not a local model, get the protocol
-      String modelProtocol = operationContext.findProtocol(model);
-      if (logger.isDebugEnabled()) {
-        logger.debug("Model " + model + " protocol is " + modelProtocol);
-      }
-
-      // find the factory for this protocol
-      ResolverFactory resolverFactory =
-          (ResolverFactory) externalResolverFactoryMap.get(modelProtocol);
-      if (resolverFactory == null) {
-        throw new QueryException(
-            "Unsupported protocol for destination model (" +
-            modelProtocol + ", " + model + " : '" + modelProtocol + "')");
-      }
-
-      return resolverFactory;
-    }
-    catch (GlobalizeException eg) {
-      throw new QueryException("Unable to globalize modeltype", eg);
-    }
-  }
-
-  //
-  // Private accessors intended only for DatabaseOperationContext
-  //
-
-  SystemResolver getSystemResolver() {
-    return systemResolver;
-  }
-
-  Transaction getTransaction() {
-    return transaction;
-  }
-
-  boolean isWriting() {
-    return writing;
-  }
-
-  boolean ensureTransactionResumed() throws QueryException {
-    if (this.transaction != null) {
-      resumeTransactionalBlock();
-      return true;
-    } else {
-      return false;
-    }
-  }
-
-
-  /**
    * Backup all the data on the specified server to a URI or an output stream.
    * The database is not changed by this method.
    *
@@ -1283,13 +584,11 @@
    * @param destinationURI Option URI of the file to backup into.
    * @throws QueryException if the backup cannot be completed.
    */
-  private synchronized void backup(OutputStream outputStream,
-      URI serverURI,
-      URI destinationURI) throws QueryException {
+  private synchronized void backup(OutputStream outputStream, URI serverURI, URI destinationURI)
+      throws QueryException {
     execute(
         new BackupOperation(outputStream, serverURI, destinationURI),
-        "Unable to backup to " + destinationURI
-        );
+        "Unable to backup to " + destinationURI);
   }
 
   //
@@ -1322,75 +621,31 @@
    * Execute an {@link Operation}.
    *
    * @param operation  the {@link Operation} to execute
-   * @param failureMessage  text to appear as the exception message if the
-   *   <var>operation</var> fails
    * @throws QueryException if the <var>operation</var> fails
    */
-  private void execute(Operation operation, String failureMessage)
-    throws QueryException
+  private void execute(Operation operation, String errorString) throws QueryException
   {
-    assert operation != null;
-
-    startTransactionalOperation(operation.isWriteOperation());
-
-    assert systemResolver != null;
     try {
-      operation.execute(operationContext, systemResolver, resolverSessionFactory, metadata);
-    } catch (Throwable e) {
-      rollbackTransactionalBlock(e);
-    } finally {
-      finishTransactionalOperation(failureMessage);
+      MulgaraTransaction transaction =
+          transactionManager.getTransaction(this, operation.isWriteOperation());
+      transaction.execute(operation, resolverSessionFactory, metadata);
+    } catch (MulgaraTransactionException em) {
+      logger.info("Error executing operation: " + errorString, em);
+      throw new QueryException(errorString, em);
     }
   }
 
-  /**
-   * Execute an {@link Operation}.
-   *
-   * @param operation  the {@link Operation} to execute
-   * @throws QueryException if the <var>operation</var> fails
-   */
-  private void executeQuery(Operation operation) throws QueryException
-  {
-    /*
-     * Transaction semantics:
-     * AC && Suspended  -> R clr E B . S
-     * AC && !Suspended -> B . S
-     * !AC              -> R clr . S
-     */
-    if (autoCommit) {
-      if (this.transaction != null) {
-        resumeTransactionalBlock();
-        endPreviousQueryTransaction();
-      }
-      beginTransactionalBlock(operation.isWriteOperation());
-    }
-    else {
-      resumeTransactionalBlock();
-    }
-
-    try {
-      operation.execute(operationContext,
-                        systemResolver,
-                        resolverSessionFactory,
-                        metadata);
-    }
-    catch (Throwable th) {
-      try {
-        logger.warn("Query failed", th);
-        rollbackTransactionalBlock(th);
-      } finally {
-        endPreviousQueryTransaction();
-        throw new QueryException("Failed to rollback failed transaction", th);
-      }
-    }
-
-    try {
-      suspendTransactionalBlock();
-    } catch (Throwable th) {
-      endPreviousQueryTransaction();
-      logger.error("Query should have thrown exception", th);
-      throw new IllegalStateException("Query should have thrown exception");
-    }
+  public DatabaseOperationContext newOperationContext(boolean writing) throws QueryException {
+    return new DatabaseOperationContext(
+        cachedResolverFactorySet,
+        externalResolverFactoryMap,
+        internalResolverFactoryMap,
+        metadata,
+        securityAdapterList,
+        temporaryModelTypeURI,
+        temporaryResolverFactory,
+        symbolicTransformationList,
+        systemResolverFactory,
+        writing);
   }
-
 }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -289,4 +289,8 @@
         new SingletonStatements(model, rdfType, modelType),
         present);
   }
+
+  public void abort() {
+    resolver.abort();
+  }
 }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -122,7 +122,7 @@
    *   external models
    * @throws IllegalArgumentException if any argument is <code>null</code>
    */
-  LocalJRDFDatabaseSession(TransactionManager transactionManager,
+  LocalJRDFDatabaseSession(MulgaraTransactionManager transactionManager,
       List securityAdapterList, List symbolicTransformationList,
       ResolverSessionFactory resolverSessionFactory,
       SystemResolverFactory systemResolverFactory,

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -199,7 +199,7 @@
     else {
       assert query != null;
 
-      Answer answer = operationContext.doQuery(systemResolver, query);
+      Answer answer = operationContext.doQuery(query);
       Variable[] vars = answer.getVariables();
       assert vars.length == 3;
       statements = new TuplesWrapperStatements(
@@ -211,6 +211,7 @@
     doModify(operationContext, systemResolver, modelURI, statements, insert);
   }
 
+
   protected void doModify(OperationContext operationContext,
                           SystemResolver   systemResolver,
                           URI              modelURI,
@@ -239,9 +240,7 @@
 
     // Obtain a resolver for the destination model type
     Resolver resolver = operationContext.obtainResolver(
-                          operationContext.findModelResolverFactory(model),
-                          systemResolver
-                        );
+                          operationContext.findModelResolverFactory(model));
     assert resolver != null;
 
     if (logger.isDebugEnabled()) {

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransaction.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransaction.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransaction.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -27,10 +27,11 @@
 
 // Local packages
 import org.mulgara.resolver.spi.DatabaseMetadata;
-import org.mulgara.resolver.spi.Resolver;
+import org.mulgara.resolver.spi.EnlistableResource;
 import org.mulgara.resolver.spi.ResolverSessionFactory;
 
 import org.mulgara.query.TuplesException;
+import org.mulgara.query.QueryException;
 
 /**
  * Responsible for the javax.transaction.Transaction object.
@@ -52,11 +53,11 @@
  *
  * @maintenanceAuthor $Author: $
  *
- * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ * @company <a href="mailto:mail at netymon.com">Netymon Pty Ltd</a>
  *
  * @copyright &copy;2006 <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
  *
- * @licence Open Software License v3.0</a>
+ * @licence Open Software License v3.0
  */
 public class MulgaraTransaction {
   /** Logger.  */
@@ -64,7 +65,7 @@
     Logger.getLogger(MulgaraTransaction.class.getName());
 
   private MulgaraTransactionManager manager;
-  private OperationContext context;
+  private DatabaseOperationContext context;
 
   private Transaction transaction;
   private Thread currentThread;
@@ -79,179 +80,384 @@
   private int rollback;
   private Throwable rollbackCause;
 
-  public MulgaraTransaction(MulgaraTransactionManager manager, OperationContext context) {
-    this.manager = manager;
-    this.context = context;
+  public MulgaraTransaction(MulgaraTransactionManager manager, DatabaseOperationContext context)
+      throws Exception {
+    report("Creating Transaction");
+    try {
+      if (manager == null) {
+        throw new IllegalArgumentException("Manager null in MulgaraTransaction");
+      } else if (context == null) {
+        throw new IllegalArgumentException("OperationContext null in MulgaraTransaction");
+      }
+      this.manager = manager;
+      this.context = context;
 
-//    FIXME: MTMgr will be null until operational.
-//    this.transaction = manager.transactionStart(this);
-    inuse = 1; // Note: This implies implict activation as a part of construction.
-    using = 0;
+      inuse = 0;
+      using = 0;
 
-    rollback = NO_ROLLBACK;
-    rollbackCause = null;
+      rollback = NO_ROLLBACK;
+      rollbackCause = null;
+    } finally {
+      report("Created Transaction");
+    }
+  }
 
-//    FIXME: need this added to context. Sets up and enlists the system-resolver.
-//    context.initiate();
 
-//    FIXME: need this added to context. Allows context to cleanup caches at end of transaction.
-//    this.transaction.enlistResource(context.getXAResource());
-  }
+  synchronized void activate() throws MulgaraTransactionException {
+    report("Activating Transaction");
+    try {
+      if (rollback != NO_ROLLBACK) {
+        throw new MulgaraTransactionException("Attempt to activate failed transaction");
+      }
 
-  // FIXME: Not yet certain I have the error handling right here.
-  // Need to clarify semantics and ensure the error conditions are 
-  // properly handled.
-  private synchronized void activate() throws MulgaraTransactionException {
-    if (currentThread == null) {
-      currentThread = Thread.currentThread();
-    } else if (!currentThread.equals(Thread.currentThread())) {
-      throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
-    }
+      if (currentThread == null) {
+        currentThread = Thread.currentThread();
+      } else if (!currentThread.equals(Thread.currentThread())) {
+        throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+      }
+      
+      if (manager == null) {
+        errorReport("Attempt to activate terminated transaction");
+        throw new MulgaraTransactionException("Attempt to activate terminated transaction");
+      }
 
-    if (inuse == 0) {
-      try {
-        manager.transactionResumed(this);
-      } catch (Throwable th) {
-        logger.warn("Error resuming transaction: ", th);
-        failTransaction();
-        throw new MulgaraTransactionException("Error resuming transaction", th);
+      if (inuse == 0) {
+        if (transaction == null) {
+          startTransaction();
+        } else {
+          resumeTransaction();
+        }
       }
+
+      inuse++;
+
+      checkActivated();
+    } finally {
+      report("Activated transaction");
     }
-
-    inuse++;
   }
 
-
-  // FIXME: Not yet certain I have the error handling right here.
-  // Need to clarify semantics and ensure the error conditions are 
-  // properly handled.
   private synchronized void deactivate() throws MulgaraTransactionException {
+    report("Deactivating transaction");
+    try {
+      if (rollback == NO_ROLLBACK) {
+        checkActivated();
+      } // rollback'd transactions are cleaned up on the final deactivation.
 
-    inuse--;
+      inuse--;
 
-    if (inuse < 0) {
-      throw implicitRollback(
-          new MulgaraTransactionException("Mismatched activate/deactivate.  inuse < 0: " + inuse));
-    }
-
-    if (inuse == 0) {
-      if (using == 0) {
-        // END TRANSACTION HERE.  But commit might fail.
-        manager.transactionComplete(this);
-      } else {
-        // What happens if suspend fails?
-        // Rollback and terminate transaction.
-        // JTA isn't entirely unambiguous as to the long-term stability of the original
-        // transaction object - can suspend return a new object?
-        this.transaction = manager.transactionSuspended(this);
+      if (inuse == 0) {
+        if (using == 0) {
+          terminateTransaction();
+        } else {
+          suspendTransaction();
+        }
+        currentThread = null;
       }
-      currentThread = null;
+    } finally {
+      report("Deactivated Transaction");
     }
   }
 
-  // Do I want to check for currentThread here?  Do I want a seperate check() method to 
-  // cover precondition checks against currentThread?
-  void reference() {
-    using++;
+  // Note: The transaction is often not activated when these are called.
+  //       This occurs when setting autocommit, as this creates and
+  //       references a transaction object that won't be started/activated
+  //       until it is first used.
+  void reference() throws MulgaraTransactionException {
+    report("Referencing Transaction");
+    try {
+      using++;
+    } finally {
+      report("Referenced Transaction");
+    }
   }
 
   void dereference() throws MulgaraTransactionException {
-    using--;
-    if (using < 0) {
-      throw implicitRollback(new MulgaraTransactionException("ERROR: Transaction dereferenced more times than referenced!"));
+    report("Dereferencing Transaction");
+    try {
+      if (using < 1) {
+        throw implicitRollback(new MulgaraTransactionException(
+            "Reference Failure.  Dereferencing while using < 1: " + using));
+      }
+      using--;
+    } finally {
+      report("Dereferenced Transaction");
     }
   }
 
   void execute(Operation operation,
                ResolverSessionFactory resolverSessionFactory, // FIXME: We shouldn't need this. - only used for backup and restore operations.
                DatabaseMetadata metadata) throws MulgaraTransactionException {
-    activate();
+    report("Executing Operation");
     try {
-//      FIXME: Need to migrate systemResolver to context for this to work.
-//      operation.execute(context,
-//                        context.getSystemResolver(),
-//                        resolverSessionFactory,
-//                        metadata);
-    } catch (Throwable th) {
-      throw implicitRollback(th);
+      activate();
+      try {
+        operation.execute(context,
+                          context.getSystemResolver(),
+                          resolverSessionFactory,
+                          metadata);
+      } catch (Throwable th) {
+        throw implicitRollback(th);
+      } finally {
+        deactivate();
+      }
     } finally {
-      deactivate();
+      report("Executed Operation");
     }
   }
 
-  /** Should rename this 'wrap' */
   AnswerOperationResult execute(AnswerOperation ao) throws TuplesException {
-//    FIXME: activate/deactivate won't work until we have MTMgr operational.
-//    activate();
+    debugReport("Executing AnswerOperation");
     try {
-      ao.execute();
-      return ao.getResult();
-    } catch (Throwable th) {
-      throw new TuplesException("Error accessing Answer", 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");
     }
   }
 
 
-  private MulgaraTransactionException implicitRollback(Throwable cause) throws MulgaraTransactionException {
-    rollback = IMPLICIT_ROLLBACK;
-    rollbackCause = cause;
-    failTransaction();
-    return new MulgaraTransactionException("Transaction Rolledback", cause);
+  void execute(TransactionOperation to) throws MulgaraTransactionException {
+    report("Executing TransactionOperation");
+    try {
+      activate();
+      try {
+        to.execute();
+      } catch (Throwable th) {
+        throw implicitRollback(th);
+      } finally {
+        deactivate();
+      }
+    } finally {
+      report("Executed TransactionOperation");
+    }
   }
 
+
+  MulgaraTransactionException implicitRollback(Throwable cause) throws MulgaraTransactionException {
+    report("Implicit Rollback triggered");
+
+    if (rollback == IMPLICIT_ROLLBACK) {
+      logger.warn("Cascading error, transaction already rolled back", cause);
+      logger.warn("Cascade error, expected initial cause", rollbackCause);
+
+      return new MulgaraTransactionException("Transaction already in rollback", cause);
+    }
+
+    try {
+      checkActivated();
+      rollback = IMPLICIT_ROLLBACK;
+      rollbackCause = cause;
+      failTransaction();
+      return new MulgaraTransactionException("Transaction in Rollback", cause);
+    } catch (Throwable th) {
+      abortTransaction("Failed to rollback normally", th);
+      throw new MulgaraTransactionException("Abort failed to throw exception", th);
+    }
+  }
+
   /**
-   * Note: I think this is the only one that matters. 
+   * Rollback the transaction.
+   * We don't throw an exception here when transaction fails - this is expected,
+   * after all we requested it.
    */
-  protected void explicitRollback() throws MulgaraTransactionException {
-    rollback = EXPLICIT_ROLLBACK;
-    // We don't throw an exception here when transaction fails - this is expected,
-    // after all we requested it.
+  public void explicitRollback() throws MulgaraTransactionException {
+    try {
+      checkActivated();
+      failTransaction();
+      rollback = EXPLICIT_ROLLBACK;
+    } catch (Throwable th) {
+      abortTransaction("Explicit rollback failed", th);
+    }
   }
 
-  private void terminateTransaction() throws MulgaraTransactionException {
+  private void startTransaction() throws MulgaraTransactionException {
+    report("Initiating transaction");
+    transaction = manager.transactionStart(this);
+    try {
+      context.initiate(this);
+    } catch (Throwable th) {
+      throw implicitRollback(th);
+    }
   }
 
-  private void failTransaction() throws MulgaraTransactionException {
-    // We need to handle the whole fact this is an error, but the core operation is rollback.
+  private void resumeTransaction() throws MulgaraTransactionException {
+    report("Resuming transaction");
     try {
-      transaction.rollback();
-    } catch (SystemException es) {
-      throw new MulgaraTransactionException("Failed to Rollback", es);
+      manager.transactionResumed(this, transaction);
+    } catch (Throwable th) {
+      abortTransaction("Failed to resume transaction", th);
     }
   }
 
-  private void finalizeTransaction() throws MulgaraTransactionException {
-    // We need a whole load of error handling here, but the core operation is commit.
+  private void suspendTransaction() throws MulgaraTransactionException {
+    report("Suspending Transaction");
     try {
-      transaction.commit();
-    } catch (Exception e) {
-      throw new MulgaraTransactionException("Error while trying to commit", e);
+      if (rollback == NO_ROLLBACK) {
+        this.transaction = manager.transactionSuspended(this);
+      } else {
+        terminateTransaction();
+      }
+    } catch (Throwable th) {
+      throw implicitRollback(th);
     } finally {
-      manager.transactionComplete(this);
+      report("Finished suspending transaction");
     }
   }
 
+  private void terminateTransaction() throws MulgaraTransactionException {
+    report("Terminating Transaction: " + rollback);
+//    errorReport("Terminating Transaction: " + rollback);
+    try {
+      switch (rollback) {
+        case NO_ROLLBACK:
+          report("Completing Transaction");
+          try {
+            transaction.commit();
+            transaction = null;
+          } catch (Throwable th) {
+            implicitRollback(th);
+            terminateTransaction();
+          }
+          break;
+        case IMPLICIT_ROLLBACK:
+          report("Completing Implicitly Failed Transaction");
+          // Check that transaction is cleaned up.
+          throw new MulgaraTransactionException(
+              "Failed transaction finalised. (ROLLBACK)", rollbackCause);
+        case EXPLICIT_ROLLBACK:
+          report("Completing explicitly failed transaction (ROLLBACK)");
+          // Check that transaction is cleaned up.
+          break;
+      }
+    } finally {
+      try {
+        try {
+          context.clear();
+        } catch (QueryException eq) {
+          throw new MulgaraTransactionException("Error clearing context", eq);
+        }
+      } finally {
+        try {
+          manager.transactionComplete(this);
+        } finally {
+          manager = null;
+          inuse = 0;
+          using = 0;
+          report("Terminated transaction");
+        }
+      }
+    }
+  }
+
+  private void failTransaction() throws Throwable {
+    transaction.rollback();
+    manager.transactionFailed(this);
+    context.clear();
+  }
+
+  void abortTransaction(String errorMessage, Throwable th) throws MulgaraTransactionException {
+    // We need to notify the manager here - this is serious, we
+    // can't rollback normally if we can't resume!  The call to
+    // context.abort() is an escape hatch we use to abort the 
+    // current phase behind the scenes.
+    logger.error(errorMessage + " - Aborting", th);
+    try {
+      manager.transactionAborted(this);
+    } finally {
+      context.abort();
+    }
+    throw new MulgaraTransactionException(errorMessage + " - Aborting", th);
+  }
+
   //
   // Note that OperationContext needs to be decoupled from DatabaseSession, so that it is
   // recreated for each operation - it also needs to provide an XAResource of it's own for
   // enlisting in the transaction to clean-up caches and the like.
   //
-  public void enlistResolver(Resolver resolver) throws MulgaraTransactionException {
+  public void enlist(EnlistableResource enlistable) throws MulgaraTransactionException {
     try {
-      XAResource resource = resolver.getXAResource();
-      transaction.enlistResource(resource);
+      transaction.enlistResource(enlistable.getXAResource());
     } catch (Exception e) {
       throw new MulgaraTransactionException("Error enlisting resolver", e);
     }
   }
 
+  //
+  // Should only be visible to MulgaraTransactionManager.
+  //
+
   /**
-   * Should only be visible to MulgaraTransactionManager.
+   * Force transaction to a conclusion.
+   * If we can't activate or commit, then rollback and terminate.
+   * This is called by the manager to indicate that it needs this transaction to
+   * be finished NOW, one way or another.
    */
-  protected Transaction getTransaction() {
-    return transaction;
+  void completeTransaction() throws MulgaraTransactionException {
+    try {
+      activate();
+    } catch (Throwable th) {
+      implicitRollback(th);  // let terminate throw this exception if required.
+    } finally {
+      terminateTransaction();
+    }
+    // We don't need to deactivate - this method is *only* for use by the
+    // MulgaraTransactionManager!
   }
+
+  protected void finalize() {
+    report("GC-finalize");
+    if (inuse != 0 || using != 0) {
+      errorReport("Referernce counting error in transaction");
+    }
+    if (manager != null || transaction != null) {
+      errorReport("Transaction not terminated properly");
+    }
+  }
+
+  //
+  // Used internally
+  //
+
+  private void checkActivated() throws MulgaraTransactionException {
+    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.");
+    } else if (inuse < 1) {
+      throw implicitRollback(
+          new MulgaraTransactionException("Mismatched activate/deactivate.  inuse < 1: " + inuse));
+    } else if (using < 0) {
+      throw implicitRollback(
+          new MulgaraTransactionException("Reference Failure.  using < 0: " + using));
+    }
+  }
+
+  private void report(String desc) {
+    if (logger.isInfoEnabled()) {
+      logger.info(desc + ": " + System.identityHashCode(this) +
+          ", inuse=" + inuse + ", using=" + using);
+    }
+  }
+
+  private void debugReport(String desc) {
+    if (logger.isDebugEnabled()) {
+      logger.debug(desc + ": " + System.identityHashCode(this) +
+          ", inuse=" + inuse + ", using=" + using);
+    }
+  }
+
+  private void errorReport(String desc) {
+    logger.error(desc + ": " + System.identityHashCode(this) +
+        ", inuse=" + inuse + ", using=" + using, new Throwable());
+  }
 }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -19,7 +19,11 @@
 
 // Java2 packages
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
+import javax.transaction.SystemException;
 import javax.transaction.Transaction;
 import javax.transaction.TransactionManager;
 import javax.transaction.xa.XAResource;
@@ -29,6 +33,7 @@
 
 // Local packages
 import org.mulgara.server.Session;
+import org.mulgara.transaction.TransactionManagerFactory;
 
 /**
  * Manages transactions within Mulgara.
@@ -68,24 +73,37 @@
   private Session currentWritingSession;
   private MulgaraTransaction userTransaction;
 
-  /** Map from session to transaction for all 'write' transactions that have been rolledback. */
-  private Map failedSessions;
+  /** Set of sessions whose transactions have been rolledback.*/
+  private Set failedSessions;
 
-  /** Map from thread to associated transaction. */
+  /**
+   * Map from transaction to initiating session.
+   * FIXME: This is only required for checking while we wait for 1-N.
+   *        Remove once 1-N is implemented.
+   */
+  private Map sessions;
+
+  /**
+   * Map from initiating session to set of transactions.
+   * Used to clean-up transactions upon session close.
+   */
+  private Map transactions;
+
+  /** Map of threads to active transactions. */
   private Map activeTransactions;
 
   private TransactionManager transactionManager;
 
-  private Object writeLockMutex;
-
-  public MulgaraTransactionManager(TransactionManager transactionManager) {
+  public MulgaraTransactionManager(TransactionManagerFactory transactionManagerFactory) {
     this.currentWritingSession = null;
     this.userTransaction = null;
 
-    this.failedSessions = new HashMap();
+    this.failedSessions = new HashSet();
+    this.sessions = new HashMap();
+    this.transactions = new HashMap();
+    this.activeTransactions = new HashMap();
 
-    this.transactionManager = transactionManager;
-    this.writeLockMutex = new Object();
+    this.transactionManager = transactionManagerFactory.newTransactionManager();
   }
 
   /**
@@ -99,50 +117,67 @@
    * </ul>
    */
   public synchronized MulgaraTransaction getTransaction(DatabaseSession session, boolean write) throws MulgaraTransactionException {
+
     if (session == currentWritingSession) {
       return userTransaction;
     } 
 
-    if (write) {
-      obtainWriteLock(session);
-    }
+    try {
+      MulgaraTransaction transaction = write ?
+          obtainWriteLock(session) :
+          new MulgaraTransaction(this, session.newOperationContext(false));
+      sessions.put(transaction, session);
 
-//    FIXME: Need to finish 1-N DS-OC and provide this method - should really be newOperationContext.
-//    return new MulgaraTransaction(this, session.getOperationContext());
-    return new MulgaraTransaction(this, null);
-  }
-
-
-  public synchronized MulgaraTransaction getTransaction()
-      throws MulgaraTransactionException {
-    MulgaraTransaction transaction = (MulgaraTransaction)activeTransactions.get(Thread.currentThread());
-    if (transaction != null) {
       return transaction;
-    } else {
-      throw new MulgaraTransactionException("No transaction assoicated with current thread");
+    } catch (MulgaraTransactionException em) {
+      throw em;
+    } catch (Exception e) {
+      throw new MulgaraTransactionException("Error creating transaction", e);
     }
   }
 
-  private synchronized void obtainWriteLock(Session session)
+
+  private synchronized MulgaraTransaction obtainWriteLock(DatabaseSession session)
       throws MulgaraTransactionException {
     while (currentWritingSession != null) {
       try {
-        writeLockMutex.wait();
+        this.wait();
       } catch (InterruptedException ei) {
         throw new MulgaraTransactionException("Interrupted while waiting for write lock", ei);
       }
     }
-    currentWritingSession = session;
+
+    try {
+      currentWritingSession = session;
+      userTransaction = new MulgaraTransaction(this, session.newOperationContext(true));
+      return userTransaction;
+    } catch (MulgaraTransactionException em) {
+      releaseWriteLock();
+      throw em;
+    } catch (Throwable th) {
+      releaseWriteLock();
+      throw new MulgaraTransactionException("Error while obtaining write-lock", th);
+    }
   }
 
   private synchronized void releaseWriteLock() {
+    // Calling this method multiple times is safe as the lock cannot be obtained
+    // between calls as this method is private, and all calling methods are
+    // synchronized.
     currentWritingSession = null;
     userTransaction = null;
-    writeLockMutex.notify();
+    this.notify();
   }
 
 
-  public synchronized void commit(Session session) throws MulgaraTransactionException {
+  public synchronized void commit(DatabaseSession session) throws MulgaraTransactionException {
+    if (failedSessions.contains(session)) {
+      throw new MulgaraTransactionException("Attempting to commit failed exception");
+    } else if (session != currentWritingSession) {
+      throw new MulgaraTransactionException(
+          "Attempting to commit while not the current writing transaction");
+    }
+
     setAutoCommit(session, true);
     setAutoCommit(session, false);
   }
@@ -153,64 +188,115 @@
    * This 
    * This needs to be distinguished from an implicit rollback triggered by failure.
    */
-  public synchronized void rollback(Session session) throws MulgaraTransactionException {
+  public synchronized void rollback(DatabaseSession session) throws MulgaraTransactionException {
     if (session == currentWritingSession) {
       try {
-        userTransaction.explicitRollback();
-        finalizeTransaction();
+        userTransaction.execute(new TransactionOperation() {
+          public void execute() throws MulgaraTransactionException {
+            userTransaction.explicitRollback();
+          }
+        });
+        if (userTransaction != null) {
+          // transaction referenced by something - need to explicitly end it.
+          userTransaction.completeTransaction();
+        }
       } finally {
-        failedSessions.put(currentWritingSession, userTransaction);
-        userTransaction = null;
-        currentWritingSession = null;
+        failedSessions.add(currentWritingSession);
         releaseWriteLock();
+        setAutoCommit(session, false);
       }
+    } else if (failedSessions.contains(session)) {
+      failedSessions.remove(session);
+      setAutoCommit(session, false);
     } else {
-      // We have a problem - rollback called on session that doesn't have a transaction active.
+      throw new MulgaraTransactionException(
+          "Attempt to rollback while not in the current writing transaction");
     }
   }
 
-  public synchronized void setAutoCommit(Session session, boolean autoCommit)
+  public synchronized void setAutoCommit(DatabaseSession session, boolean autoCommit)
       throws MulgaraTransactionException {
-    if (session == currentWritingSession && failedSessions.containsKey(session)) {
-      // CRITICAL ERROR - transaction failed, but we did not finalise it.
+    if (session == currentWritingSession && failedSessions.contains(session)) {
+      userTransaction.abortTransaction("Session failed and transaction not finalized",
+          new MulgaraTransactionException("Failed Session in setAutoCommit"));
     }
 
-    if (session == currentWritingSession || failedSessions.containsKey(session)) {
+    if (session == currentWritingSession || failedSessions.contains(session)) {
       if (autoCommit) {
         // AutoCommit off -> on === branch on current state of transaction.
         if (session == currentWritingSession) {
           // Within active transaction - commit and finalise.
           try {
+            userTransaction.dereference();
+            userTransaction.completeTransaction();
           } finally {
             releaseWriteLock();
           }
-        } else {
+        } else if (failedSessions.contains(session)) {
           // Within failed transaction - cleanup and finalise.
           failedSessions.remove(session);
         }
       } else {
+        logger.info("Attempt to set autocommit false twice");
         // AutoCommit off -> off === no-op. Log info.
       }
     } else {
       if (autoCommit) {
         // AutoCommit on -> on === no-op.  Log info.
+        logger.info("Attempting to set autocommit true without setting it false");
       } else {
         // AutoCommit on -> off == Start new transaction.
-        obtainWriteLock(session);
-//        FIXME: finish DS-OC first.
-//        userTransaction = new MulgaraTransaction(this, session.newOperationContext(true));
-        currentWritingSession = session;
+        userTransaction = getTransaction(session, true);
+        userTransaction.reference();
       }
     }
   }
 
-  public synchronized void closingSession(Session session) throws MulgaraTransactionException {
-    // Check if we hold the write lock, if we do then rollback and throw exception.
-    // Otherwise - no-op.
-  }
+  public synchronized void terminateCurrentTransactions(Session session)
+      throws MulgaraTransactionException {
+    if (failedSessions.contains(session)) {
+      failedSessions.remove(session);
+      return;
+    }
 
-  public void finalizeTransaction() {
-    throw new IllegalStateException("mmmm I was doing something here. I need to remember what.");
+    Throwable error = null;
+    try {
+      if (session == currentWritingSession) {
+        logger.error("Terminating session while holding writelock:" + session +
+            ":" + currentWritingSession + ": " + userTransaction);
+        userTransaction.execute(new TransactionOperation() {
+            public void execute() throws MulgaraTransactionException {
+              userTransaction.implicitRollback(
+                new MulgaraTransactionException("Terminating session while holding writelock"));
+            }
+        });
+      }
+    } catch (Throwable th) {
+      error = th;
+    }
+
+    Set trans = (Set)transactions.get(session);
+    if (trans != null) {
+      Iterator i = trans.iterator();
+      while (i.hasNext()) {
+        MulgaraTransaction transaction = (MulgaraTransaction)i.next();
+        logger.error("Active transaction during session termination");
+        Throwable th = transaction.implicitRollback(
+            new MulgaraTransactionException("Terminating session during active transaction"));
+        if (error ==  null) {
+          error = th;
+        }
+        transaction.completeTransaction();
+      }
+    }
+
+    if (error != null) {
+      if (error instanceof MulgaraTransactionException) {
+        throw (MulgaraTransactionException)error;
+      } else {
+        throw new MulgaraTransactionException("Aborting session on close", error);
+      }
+    }
   }
 
   //
@@ -220,6 +306,14 @@
   public synchronized Transaction transactionStart(MulgaraTransaction transaction)
       throws MulgaraTransactionException {
     try {
+      logger.info("Beginning Transaction");
+      if (activeTransactions.get(Thread.currentThread()) != null) {
+        throw new MulgaraTransactionException(
+            "Attempt to start transaction in thread with exiting active transaction.");
+      } else if (activeTransactions.containsValue(transaction)) {
+        throw new MulgaraTransactionException("Attempt to start transaction twice");
+      }
+
       transactionManager.begin();
       Transaction jtaTrans = transactionManager.getTransaction();
 
@@ -231,18 +325,17 @@
     }
   }
 
-  public synchronized void transactionResumed(MulgaraTransaction transaction) 
+  public synchronized void transactionResumed(MulgaraTransaction transaction, Transaction jtaXA) 
       throws MulgaraTransactionException {
     if (activeTransactions.get(Thread.currentThread()) != null) {
       throw new MulgaraTransactionException(
           "Attempt to resume transaction in already activated thread");
     } else if (activeTransactions.containsValue(transaction)) {
-      throw new MulgaraTransactionException(
-          "Attempt to resume resumed transaction");
+      throw new MulgaraTransactionException("Attempt to resume active transaction");
     }
     
     try {
-      transactionManager.resume(transaction.getTransaction());
+      transactionManager.resume(jtaXA);
       activeTransactions.put(Thread.currentThread(), transaction);
     } catch (Exception e) {
       throw new MulgaraTransactionException("Resume Failed", e);
@@ -259,24 +352,56 @@
 
       return transactionManager.suspend();
     } catch (Exception e) {
+      logger.error("Attempt to suspend failed", e);
+      try {
+        transactionManager.setRollbackOnly();
+      } catch (Throwable th) {
+        logger.error("Attempt to setRollbackOnly() failed", th);
+      }
       throw new MulgaraTransactionException("Suspend failed", e);
     } finally {
       activeTransactions.remove(Thread.currentThread());
     }
-
   }
 
   public synchronized void transactionComplete(MulgaraTransaction transaction) 
       throws MulgaraTransactionException {
+    if (holdsWriteLock(transaction)) {
+      releaseWriteLock();
+    }
+
+    activeTransactions.remove(Thread.currentThread());
+    Session session = (Session)sessions.get(transaction);
+    sessions.remove(transaction);
+    transactions.remove(session);
+  }
+
+  public synchronized void transactionFailed(MulgaraTransaction transaction) {
+    // No specific behaviour required here.
+  }
+
+  public synchronized void transactionAborted(MulgaraTransaction transaction) {
     try {
-      transactionManager.commit();
+      // Make sure this cleans up the transaction metadata - this transaction is DEAD!
       if (transaction == userTransaction) {
-        releaseWriteLock();
+        failedSessions.add(currentWritingSession);
       }
-    } catch (Exception e) {
-      throw new MulgaraTransactionException("Commit Failed", e);
-    } finally {
-      activeTransactions.remove(Thread.currentThread());
+      transactionComplete(transaction);
+    } catch (Throwable th) {
+      // FIXME: This should probably abort the entire server after logging the error!
+      logger.error("Error managing transaction abort", th);
     }
   }
+
+  public void setTransactionTimeout(int transactionTimeout) {
+    try {
+      transactionManager.setTransactionTimeout(transactionTimeout);
+    } catch (SystemException es) {
+      logger.warn("Unable to set transaction timeout: " + transactionTimeout, es);
+    }
+  }
+
+  private boolean holdsWriteLock(MulgaraTransaction transaction) {
+    return transaction == userTransaction;
+  }
 }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/OperationContext.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/OperationContext.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/OperationContext.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -103,8 +103,7 @@
    * @throws QueryException if the {@link Resolver} couldn't be obtained, bound
    *   and enlisted
    */
-  public Resolver obtainResolver(ResolverFactory resolverFactory,
-                                 SystemResolver  systemResolver)
+  public Resolver obtainResolver(ResolverFactory resolverFactory)
     throws QueryException;
 
 
@@ -121,5 +120,7 @@
    * Oct 2006 - if it's still here after Dec 2006 let someone know it's been
    * forgotten.
    */
-  public Answer doQuery(SystemResolver systemResolver, Query query) throws Exception;
+  public Answer doQuery(Query query) throws Exception;
+
+  public SystemResolver getSystemResolver(); // FIXME: Scaffolding for transactions.
 }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/QueryOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/QueryOperation.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/QueryOperation.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -153,14 +153,14 @@
     if (query != null) {
       assert queryList == null;
 
-      answer = operationContext.doQuery(systemResolver, query);
+      answer = operationContext.doQuery(query);
     }
     else {
       assert queryList != null;
 
       answerList = new ArrayList(queryList.size());
       for (Iterator i = queryList.iterator(); i.hasNext();) {
-        answerList.add(operationContext.doQuery(systemResolver, (Query) i.next()));
+        answerList.add(operationContext.doQuery((Query) i.next()));
       }
     }
   }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/RemoveModelOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/RemoveModelOperation.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/RemoveModelOperation.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -155,8 +155,7 @@
     }
 
     // Obtain an appropriate resolver bound to this session
-    Resolver resolver =
-      operationContext.obtainResolver(resolverFactory, systemResolver);
+    Resolver resolver = operationContext.obtainResolver(resolverFactory);
     assert resolver != null;
 
     // Use the resolver to remove the model

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/SetModelOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/SetModelOperation.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/SetModelOperation.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -152,9 +152,7 @@
 
     // Obtain a resolver for the destination model type
     Resolver destinationResolver = operationContext.obtainResolver(
-      operationContext.findModelResolverFactory(destinationModel),
-      systemResolver
-    );
+      operationContext.findModelResolverFactory(destinationModel));
     assert destinationResolver != null;
 
     ContentHandler contentHandler = contentHandlers.getContentHandler(content);

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/StatusFormat.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/StatusFormat.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/StatusFormat.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -30,7 +30,6 @@
 // Java 2 enterprise packages
 import javax.transaction.Status;
 import javax.transaction.SystemException;
-import javax.transaction.TransactionManager;
 
 /**
  * Generate a presentation form for a transaction {@link Status}.
@@ -89,7 +88,8 @@
    *
    * @param transactionManager  the transaction manager
    */
-  public static String formatStatus(TransactionManager transactionManager)
+   /*
+  public static String formatStatus(MulgaraTransactionManager transactionManager)
   {
     try {
       return formatStatus(transactionManager.getStatus());
@@ -98,4 +98,5 @@
       return e.getMessage();
     }
   }
+  */
 }

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/StringPoolSession.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/StringPoolSession.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/StringPoolSession.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -491,6 +491,7 @@
         } else {
           nodeId = persistentNodePool.newNode();
         }
+
         return nodeId;
       }
       // If it's a read phase and not the local BlankNode then throw an

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/SubqueryAnswer.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/SubqueryAnswer.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/SubqueryAnswer.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -76,7 +76,7 @@
   private Variable[] variables;
 
   /** The current database session for this query. */
-  protected AnswerDatabaseSession databaseSession;
+  protected OperationContext operationContext;
 
   /**
    * Assignment property.
@@ -115,12 +115,11 @@
    * @throws TuplesException if it fails to get the row cardinality of the
    *   given tuples.
    */
-  SubqueryAnswer(AnswerDatabaseSession session, ResolverSession resolverSession,
+  SubqueryAnswer(OperationContext operationContext, ResolverSession resolverSession,
       Tuples tuples, List variableList) throws TuplesException {
     super(tuples, resolverSession);
 
-    this.databaseSession = session;
-    this.databaseSession.registerAnswer(this);
+    this.operationContext = operationContext;
     assignVariables(tuples, variableList);
   }
 
@@ -185,7 +184,6 @@
     cloned.variables = new Variable[this.variables.length];
     System.arraycopy(this.variables, 0, cloned.variables, 0,
         this.variables.length);
-    databaseSession.registerAnswer(cloned);
     return cloned;
   }
 
@@ -194,16 +192,6 @@
   //
 
 
-  public void close() throws TuplesException {
-    super.close();
-    try {
-      databaseSession.deregisterAnswer(this);
-    }
-    catch (QueryException eq) {
-      logger.info("Failed to deregister answer from session", eq);
-    }
-  }
-
   public int getColumnIndex(Variable variable) throws TuplesException {
     if (variable == null) {
       throw new IllegalArgumentException("Null \"variable\" parameter");
@@ -313,7 +301,7 @@
         logger.debug("Generated subquery: " + query);
       }
 
-      return databaseSession.innerQuery(query);
+      return operationContext.doQuery(query);
     }
     catch (Exception e) {
       throw new QueryException("Failed to resolve subquery", e);

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/SubqueryAnswerUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/SubqueryAnswerUnitTest.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/SubqueryAnswerUnitTest.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -160,7 +160,7 @@
    */
   public void testVariableMappings() throws Exception {
     ResolverSession testResolver = new TestResolverSession();
-    AnswerDatabaseSession testDbSession = new TestAnswerDatabaseSession();
+    OperationContext testContext = new TestOperationContext();
 
     Variable varX = new Variable("x");
     Variable varY = new Variable("y");
@@ -177,7 +177,7 @@
     variableList.add(varY);
 
     // Create subquery.
-    SubqueryAnswer answer = new SubqueryAnswer(testDbSession, testResolver,
+    SubqueryAnswer answer = new SubqueryAnswer(testContext, testResolver,
         tuples, variableList);
 
     // Get column index of Z.
@@ -188,19 +188,23 @@
     assertEquals("Based on variable list Z should be", 1, columnIndexZ);
   }
 
-  private class TestAnswerDatabaseSession implements AnswerDatabaseSession {
+  private class TestOperationContext implements OperationContext {
+    public ResolverFactory findModelResolverFactory(long model)
+      throws QueryException { return null; }
 
-    public TestAnswerDatabaseSession() {
-    }
+    public ResolverFactory findModelTypeResolverFactory(URI modelTypeURI)
+      throws QueryException { return null; }
 
-    public Answer innerQuery(Query query) throws QueryException {
-      return null;
-    }
+    public List getSecurityAdapterList() { return null; }
 
-    public void registerAnswer(SubqueryAnswer answer) {
-    }
+    public Resolver obtainResolver(ResolverFactory resolverFactory)
+      throws QueryException { return null; }
 
-    public void deregisterAnswer(SubqueryAnswer answer) throws QueryException {
-    }
+    public long getCanonicalModel(long model) { return 0; }
+
+    public Answer doQuery(Query query)
+    throws Exception { return null; }
+
+    public SystemResolver getSystemResolver() { return null; } // FIXME: Scaffolding for transactions.
   }
 }

Added: trunk/src/jar/resolver/java/org/mulgara/resolver/TransactionOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/TransactionOperation.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/TransactionOperation.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -0,0 +1,24 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com). Portions created
+ * by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd.
+ * All Rights Reserved.
+ */
+
+package org.mulgara.resolver;
+
+import org.mulgara.query.TuplesException;
+
+interface TransactionOperation {
+  public void execute() throws MulgaraTransactionException;
+}

Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/TransactionalAnswer.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/TransactionalAnswer.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/TransactionalAnswer.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -17,6 +17,10 @@
 
 package org.mulgara.resolver;
 
+// Third party packages
+import org.apache.log4j.Logger;
+
+// Local packages
 import org.mulgara.query.Answer;
 import org.mulgara.query.TuplesException;
 import org.mulgara.query.Variable;
@@ -45,18 +49,39 @@
  */
 
 public class TransactionalAnswer implements Answer {
+  /** Logger.  */
+  private static final Logger logger =
+    Logger.getLogger(TransactionalAnswer.class.getName());
 
   private Answer answer;
 
   private MulgaraTransaction transaction;
 
-  public TransactionalAnswer(MulgaraTransaction transaction, Answer answer) {
-    this.answer = answer;
-    this.transaction = transaction;
-    transaction.reference();
+  private boolean closing;
+
+  public TransactionalAnswer(MulgaraTransaction transaction, Answer answer) throws TuplesException {
+    try {
+      report("Creating Answer");
+
+      if (transaction == null) {
+        throw new IllegalArgumentException("Transaction null in TransactionalAnswer");
+      } else if (answer == null) {
+        throw new IllegalArgumentException("Answer null in TransactionalAnswer");
+      }
+
+      this.answer = answer;
+      this.closing = false;
+      this.transaction = transaction;
+      transaction.reference();
+
+      report("Created Answer");
+    } catch (MulgaraTransactionException em) {
+      throw new TuplesException("Failed to associate with transaction", em);
+    }
   }
 
   public Object getObject(final int column) throws TuplesException {
+    notClosed();
     return transaction.execute(new AnswerOperation() {
         public void execute() throws TuplesException {
           returnObject(answer.getObject(column));
@@ -65,6 +90,7 @@
   }
 
   public Object getObject(final String columnName) throws TuplesException {
+    notClosed();
     return transaction.execute(new AnswerOperation() {
         public void execute() throws TuplesException {
           returnObject(answer.getObject(columnName));
@@ -73,6 +99,7 @@
   }
 
   public void beforeFirst() throws TuplesException {
+    notClosed();
     transaction.execute(new AnswerOperation() {
         public void execute() throws TuplesException {
           answer.beforeFirst();
@@ -81,19 +108,35 @@
   }
 
   public void close() throws TuplesException {
-    transaction.execute(new AnswerOperation() {
-        public void execute() throws TuplesException {
-          answer.close();
-          try {
-            transaction.dereference();
-          } catch (MulgaraTransactionException em) {
-            throw new TuplesException("Error dereferencing transaction", em);
+    report("Closing Answer");
+    if (closing) {
+      report("Deferring close to enclosing call");
+      return;
+    }
+    try {
+      notClosed();
+      closing = true;
+      transaction.execute(new AnswerOperation() {
+          public void execute() throws TuplesException {
+            answer.close();
+            try {
+              transaction.dereference();
+            } catch (MulgaraTransactionException em) {
+              throw new TuplesException("Error dereferencing transaction", em);
+            }
           }
-        }
-      });
+        });
+    } finally {
+      // !!FIXME: Note - We will need to add checks for null to all operations.
+      closing = false;
+      transaction = null;
+      answer = null;    // Note this permits the gc of the answer.
+      report("Closed Answer");
+    }
   }
 
   public int getColumnIndex(final Variable column) throws TuplesException {
+    notClosed();
     return transaction.execute(new AnswerOperation() {
         public void execute() throws TuplesException {
           returnInt(answer.getColumnIndex(column));
@@ -103,29 +146,32 @@
 
   public int getNumberOfVariables() {
     try {
+      notClosed();
       return transaction.execute(new AnswerOperation() {
           public void execute() {
             returnInt(answer.getNumberOfVariables());
           }
         }).getInt();
     } catch (TuplesException et) {
-      throw new IllegalStateException("Doesn't throw TuplesException", et);
+      throw new IllegalStateException(et.getMessage(), et);
     }
   }
 
   public Variable[] getVariables() {
     try {
+      notClosed();
       return (Variable[])(transaction.execute(new AnswerOperation() {
           public void execute() {
             returnObject(answer.getVariables());
           }
         }).getObject());
     } catch (TuplesException et) {
-      throw new IllegalStateException("Doesn't throw TuplesException", et);
+      throw new IllegalStateException(et.getMessage(), et);
     }
   }
 
   public boolean isUnconstrained() throws TuplesException {
+    notClosed();
     return transaction.execute(new AnswerOperation() {
         public void execute() throws TuplesException {
           returnBoolean(answer.isUnconstrained());
@@ -134,6 +180,7 @@
   }
 
   public long getRowCount() throws TuplesException {
+    notClosed();
     return transaction.execute(new AnswerOperation() {
         public void execute() throws TuplesException {
           returnLong(answer.getRowCount());
@@ -142,6 +189,7 @@
   }
 
   public long getRowUpperBound() throws TuplesException {
+    notClosed();
     return transaction.execute(new AnswerOperation() {
         public void execute() throws TuplesException {
           returnLong(answer.getRowUpperBound());
@@ -150,6 +198,7 @@
   }
 
   public int getRowCardinality() throws TuplesException {
+    notClosed();
     return transaction.execute(new AnswerOperation() {
         public void execute() throws TuplesException {
           returnInt(answer.getRowCardinality());
@@ -158,6 +207,7 @@
   }
 
   public boolean next() throws TuplesException {
+    notClosed();
     return transaction.execute(new AnswerOperation() {
         public void execute() throws TuplesException {
           returnBoolean(answer.next());
@@ -174,6 +224,41 @@
       return c;
     } catch (CloneNotSupportedException ec) {
       throw new IllegalStateException("Clone failed on Cloneable");
+    } catch (MulgaraTransactionException em) {
+      throw new IllegalStateException("Failed to associate with transaction", em);
     }
   }
+
+  private void report(String desc) {
+    if (logger.isInfoEnabled()) {
+      logger.info(desc + ": " + System.identityHashCode(this) + ", xa=" + System.identityHashCode(transaction));
+    }
+  }
+
+  private void warnReport(String desc) {
+    logger.warn(desc + ": " + System.identityHashCode(this) + ", xa=" + System.identityHashCode(transaction));
+  }
+
+  public void finalize() {
+    report("GC-finalizing");
+    if (transaction != null) {
+      logger.warn("TransactionalAnswer not closed");
+    }
+  }
+
+
+  void sessionClose() throws TuplesException {
+    if (answer != null) {
+      report("Session forced close");
+      close();
+    }
+  }
+
+  private void notClosed() throws TuplesException {
+    if (transaction == null) {
+      throw new TuplesException("TransactionalAnswer closed");
+    } else if (answer == null) {
+      throw new TuplesException("TransactionAnswer not closed, but Answer null");
+    }
+  }
 }

Modified: trunk/src/jar/resolver-filesystem/java/org/mulgara/resolver/filesystem/FileSystemResolver.java
===================================================================
--- trunk/src/jar/resolver-filesystem/java/org/mulgara/resolver/filesystem/FileSystemResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-filesystem/java/org/mulgara/resolver/filesystem/FileSystemResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -474,4 +474,6 @@
       throw new ResolverException("Invalid URI", eu);
     }
   }
+
+  public void abort() {}
 }

Modified: trunk/src/jar/resolver-gis/java/org/mulgara/resolver/gis/ReadOnlyGISResolver.java
===================================================================
--- trunk/src/jar/resolver-gis/java/org/mulgara/resolver/gis/ReadOnlyGISResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-gis/java/org/mulgara/resolver/gis/ReadOnlyGISResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -222,4 +222,9 @@
      */
     return resolver.resolve(constraint);
   }
+
+  public void abort() {
+    resolver.abort();
+  }
+
 }

Modified: trunk/src/jar/resolver-lucene/java/org/mulgara/resolver/lucene/LuceneResolver.java
===================================================================
--- trunk/src/jar/resolver-lucene/java/org/mulgara/resolver/lucene/LuceneResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-lucene/java/org/mulgara/resolver/lucene/LuceneResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -347,4 +347,6 @@
   private FullTextStringIndex openFullTextStringIndex(long model) throws FullTextStringIndexException {
     return new FullTextStringIndex(new File(directory, Long.toString(model)).toString(), "gn"+model );
   }
+
+  public void abort() {}
 }

Modified: trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolver.java
===================================================================
--- trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -49,6 +49,7 @@
 import org.mulgara.store.tuples.AbstractTuples;
 import org.mulgara.store.tuples.Tuples;
 import org.mulgara.store.xa.XAResolverSession;
+import org.mulgara.store.xa.SimpleXAResourceException;
 
 /**
  * Resolves constraints in models stored on the Java heap.
@@ -380,4 +381,18 @@
 
     return buffer.toString();
   }
+
+  public void abort() {
+    if (xaResolverSession != null) {
+      try {
+        try {
+          xaResolverSession.rollback();
+        } finally {
+          xaResolverSession.release();
+        }
+      } catch (SimpleXAResourceException es) {
+        throw new IllegalStateException("Error aborting resolver session", es);
+      }
+    }
+  }
 }

Modified: trunk/src/jar/resolver-nodetype/java/org/mulgara/resolver/nodetype/NodeTypeResolver.java
===================================================================
--- trunk/src/jar/resolver-nodetype/java/org/mulgara/resolver/nodetype/NodeTypeResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-nodetype/java/org/mulgara/resolver/nodetype/NodeTypeResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -371,4 +371,5 @@
     // no-op
   }
 
+  public void abort() {}
 }

Modified: trunk/src/jar/resolver-prefix/java/org/mulgara/resolver/prefix/PrefixResolver.java
===================================================================
--- trunk/src/jar/resolver-prefix/java/org/mulgara/resolver/prefix/PrefixResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-prefix/java/org/mulgara/resolver/prefix/PrefixResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -336,4 +336,5 @@
     // no-op
   }
 
+  public void abort() {}
 }

Modified: trunk/src/jar/resolver-relational/java/org/mulgara/resolver/relational/RelationalResolver.java
===================================================================
--- trunk/src/jar/resolver-relational/java/org/mulgara/resolver/relational/RelationalResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-relational/java/org/mulgara/resolver/relational/RelationalResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -338,4 +338,9 @@
       throw new ResolverException("Invalid URI", eu);
     }
   }
+
+  public void abort() {
+    // We need to clear the JDBC connections here, but we don't have a handle on
+    // the Resolutions.
+  }
 }

Modified: trunk/src/jar/resolver-spi/java/org/mulgara/content/ContentResolver.java
===================================================================
--- trunk/src/jar/resolver-spi/java/org/mulgara/content/ContentResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-spi/java/org/mulgara/content/ContentResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -305,6 +305,11 @@
     throw new QueryException("Unable to parse " + content.getURI());
   }
 
+  public void abort() {
+    // I don't believe there is anything to do here.  It is possible that we may
+    // need to close file handles or clear caches.
+  }
+
   //
   // SPI methods
   //

Added: trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/EnlistableResource.java
===================================================================
--- trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/EnlistableResource.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/EnlistableResource.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -0,0 +1,58 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com). Portions
+ * created
+ * by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd.
+ * All Rights Reserved.
+ */
+
+package org.mulgara.resolver.spi;
+
+// Java 2 standard packages
+import javax.transaction.xa.XAResource;
+
+/**
+ * A resource that can participate within a JTA transaction.
+ *
+ * @created 2006-11-14
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ * @maintenanceAuthor $Author: andrae $
+ * @company <a href="mailto:mail at netymon.com">Netymon Pty Ltd</a>
+ * @copyright &copy;2006 <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
+ * @licence Open Software License v3.0
+ */
+
+public interface EnlistableResource
+{
+  /**
+   * Expose a callback object for enlistment by a transaction manager.
+   *
+   * Note: Resources that do not wish to participate in the transaction 
+   *       should return a new DummyXAResource instead.
+   *
+   * @return an {@link XAResource} that can be used by a transaction manager to
+   *   coordinate this resolver's participation in a distributed transaction
+   * @see javax.resource.spi.ManagedConnection#getXAResource
+   */
+  public XAResource getXAResource();
+
+  /**
+   * Abort current transaction; release all resources.
+   *
+   * This method is called in the event of a catastrophic transaction control
+   * failure that has rendered it impossible to terminate the transaction
+   * normally - even as a rollback.  The resource should treat this as a
+   * rollback operation, abort all updates, and release all resources.
+   */
+  public void abort();
+}

Modified: trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/LocalSession.java
===================================================================
--- trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/LocalSession.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/LocalSession.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -56,55 +56,5 @@
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
 public interface LocalSession extends Session {
-
-  /**
-   * Start's or resumes a transaction for an operation.
-   *
-   * Using start/finish TransactionalOperation ensures properly matched pairs of
-   * begin/end and suspend/resume.
-   *
-   */
-  public void startTransactionalOperation(boolean needsWrite)
-      throws QueryException;
-
-  /**
-   * Mark the current transaction for rollback due to an exception.
-   *
-   * @param throwable  the exception which caused the rollback
-   */
-  public void rollbackTransactionalBlock(Throwable throwable)
-      throws QueryException;
-
-  /**
-   * Ends's or suspends a transaction for an operation.
-   *
-   * Using start/finish TransactionalOperation ensures properly matched pairs of
-   * begin/end and suspend/resume.
-   *
-   */
-  public void finishTransactionalOperation(String errorString)
-      throws QueryException;
-
-  /**
-   * Resumes the previously suspended transaction from the current session.
-   *
-   * @throws QueryException Must be called outside the try/catch(Throwable) block
-   * protecting the transaction.
-   */
-  public void resumeTransactionalBlock() throws QueryException;
-
-  /**
-   * Suspends current transaction, storing it in session for latter resumption.
-   *
-   * @throws Throwable Must be called inside the try/catch(Throwable) block
-   * protecting the transaction.
-   */
-  public void suspendTransactionalBlock() throws Throwable;
-
-  /**
-   * Returns the current Resolver Session.
-   *
-   * @return the current resolver session.
-   */
   public ResolverSession getResolverSession();
 }

Modified: trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/Resolver.java
===================================================================
--- trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/Resolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/Resolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -52,7 +52,7 @@
  * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
  */
 
-public interface Resolver
+public interface Resolver extends EnlistableResource
 {
   /**
    * Create a model of a specified type.
@@ -75,18 +75,6 @@
   public void createModel(long model, URI modelType) throws ResolverException, LocalizeException;
                                                                                 
   /**
-   * Expose a callback object for enlistment by a transaction manager.
-   *
-   * Note: Only the primary store can currently be transactional, so this method
-   *       is best ignored for now.  return a new DummyXAResource instead.
-   *
-   * @return an {@link XAResource} that can be used by a transaction manager to
-   *   coordinate this resolver's participation in a distributed transaction
-   * @see javax.resource.spi.ManagedConnection#getXAResource
-   */
-  public XAResource getXAResource();
-
-  /**
    * Insert or delete RDF statements in an existing model.
    *
    * @param model  the local node identifying an existing model

Modified: trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolver.java
===================================================================
--- trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -56,6 +56,7 @@
 import org.mulgara.store.tuples.Tuples;
 import org.mulgara.store.tuples.TuplesOperations;
 import org.mulgara.store.xa.SimpleXAResource;
+import org.mulgara.store.xa.SimpleXAResourceException;
 import org.mulgara.store.xa.XAResolverSession;
 import org.mulgara.store.xa.XAStatementStore;
 
@@ -525,4 +526,25 @@
                                constraintElement.getClass() + ")");
     }
   }
+
+
+  public void abort() {
+    try {
+      try {
+        statementStore.rollback();
+      } finally {
+        try {
+          xaResolverSession.rollback();
+        } finally {
+          try {
+            statementStore.release();
+          } finally {
+            xaResolverSession.release();
+          }
+        }
+      }
+    } catch (SimpleXAResourceException es) {
+      throw new IllegalStateException("Failed to Abort store", es);
+    }
+  }
 }

Modified: trunk/src/jar/resolver-test/java/org/mulgara/resolver/test/TestResolver.java
===================================================================
--- trunk/src/jar/resolver-test/java/org/mulgara/resolver/test/TestResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-test/java/org/mulgara/resolver/test/TestResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -137,4 +137,6 @@
       throw new QueryException("Failed to resolve constraint", et);
     }
   }
+
+  public void abort() {}
 }

Modified: trunk/src/jar/resolver-url/java/org/mulgara/resolver/url/URLResolver.java
===================================================================
--- trunk/src/jar/resolver-url/java/org/mulgara/resolver/url/URLResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-url/java/org/mulgara/resolver/url/URLResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -363,4 +363,6 @@
       throw new QueryException("Couldn't read URL " + modelURIReference, e);
     }
   }
+
+  public void abort() {}
 }

Modified: trunk/src/jar/resolver-view/java/org/mulgara/resolver/view/ViewResolver.java
===================================================================
--- trunk/src/jar/resolver-view/java/org/mulgara/resolver/view/ViewResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-view/java/org/mulgara/resolver/view/ViewResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -395,4 +395,6 @@
       throw new ResolverException("Invalid URI", eu);
     }
   }
+
+  public void abort() {}
 }

Modified: trunk/src/jar/resolver-xsd/java/org/mulgara/resolver/xsd/XSDResolver.java
===================================================================
--- trunk/src/jar/resolver-xsd/java/org/mulgara/resolver/xsd/XSDResolver.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/resolver-xsd/java/org/mulgara/resolver/xsd/XSDResolver.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -452,4 +452,6 @@
       throw new Error("Unsupported constraint element: " + constraintElement);
     }
   }
+
+  public void abort() {}
 }

Modified: trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/BlankNodeWrapperAnswer.java
===================================================================
--- trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/BlankNodeWrapperAnswer.java	2006-12-13 12:29:00 UTC (rev 156)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/BlankNodeWrapperAnswer.java	2007-01-03 21:32:43 UTC (rev 157)
@@ -69,7 +69,7 @@
   /**
    * The wrapped instance.
    */
-  protected final Answer answer;
+  protected Answer answer;
 
   /**
    * The blank node map.
@@ -93,6 +93,7 @@
   public Object clone() {
     try {
       BlankNodeWrapperAnswer cloned = (BlankNodeWrapperAnswer) super.clone();
+      cloned.answer = (Answer)this.answer.clone();
       return cloned;
     }
     catch (CloneNotSupportedException e) {




More information about the Mulgara-svn mailing list