[Mulgara-svn] r691 - in trunk: . docs/site-src/design jxdata/iTQL/data_types jxdata/iTQL/standard_queries lib src/jar/itql/java/org/mulgara/itql src/jar/krule/java/org/mulgara/krule src/jar/query/java/org/mulgara/query src/jar/query/java/org/mulgara/server src/jar/resolver src/jar/resolver/java/org/mulgara/resolver src/jar/resolver-distributed/java/org/mulgara/resolver/distributed src/jar/resolver-memory/java/org/mulgara/resolver/memory src/jar/resolver-spi/java/org/mulgara/resolver/spi src/jar/resolver-store/java/org/mulgara/resolver/store src/jar/resolver-test/java/org/mulgara/resolver/test src/jar/resolver-view/java/org/mulgara/resolver/view src/jar/server-beep/java/org/mulgara/server/beep src/jar/server-rmi/java/org/mulgara/server/rmi src/jar/util/java/org/mulgara/util
andrae at mulgara.org
andrae at mulgara.org
Tue Mar 18 09:32:15 UTC 2008
Author: andrae
Date: 2008-03-18 02:32:14 -0700 (Tue, 18 Mar 2008)
New Revision: 691
Added:
trunk/docs/site-src/design/JTAInterface.txt
trunk/lib/carol-2.0.5.jar
trunk/lib/howl-logger-0.1.11.jar
trunk/lib/jotm-2.0.10.jar
trunk/lib/jotm_jrmp_stubs-2.0.10.jar
trunk/src/jar/query/java/org/mulgara/query/SelectElement.java
trunk/src/jar/query/java/org/mulgara/server/ResourceManagerInstanceAdaptor.java
trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/AbstractXAResource.java
trunk/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java
trunk/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java
trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java
trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransactionFactory.java
trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransaction.java
trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransactionFactory.java
trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java
trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java
trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResource.java
trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResourceWrapperXAResource.java
trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/XAResourceWrapperRemoteXAResource.java
trunk/src/jar/util/java/org/mulgara/util/Assoc1toNMap.java
Removed:
trunk/lib/jotm-1.5.3-patched.jar
trunk/lib/jotm_carol-1.5.3.jar
trunk/lib/jotm_iiop_stubs-1.5.3.jar
Modified:
trunk/.classpath
trunk/LEGAL.txt
trunk/build.properties
trunk/build.sh
trunk/build.xml
trunk/common.xml
trunk/jxdata/iTQL/data_types/queryResult34.txt
trunk/jxdata/iTQL/standard_queries/queryResult17.txt
trunk/src/jar/itql/java/org/mulgara/itql/TqlInterpreter.java
trunk/src/jar/itql/java/org/mulgara/itql/VariableBuilder.java
trunk/src/jar/krule/java/org/mulgara/krule/QueryStruct.java
trunk/src/jar/query/java/org/mulgara/query/AggregateFunction.java
trunk/src/jar/query/java/org/mulgara/query/ConstantValue.java
trunk/src/jar/query/java/org/mulgara/query/Query.java
trunk/src/jar/query/java/org/mulgara/query/QueryUnitTest.java
trunk/src/jar/query/java/org/mulgara/query/Variable.java
trunk/src/jar/query/java/org/mulgara/server/Session.java
trunk/src/jar/resolver-distributed/java/org/mulgara/resolver/distributed/NetworkDelegator.java
trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolver.java
trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolverFactory.java
trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryXAResource.java
trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/DummyXAResource.java
trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/TripleSetWrapperStatements.java
trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolver.java
trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolverFactory.java
trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreXAResource.java
trunk/src/jar/resolver-test/java/org/mulgara/resolver/test/TestResolverUnitTest.java
trunk/src/jar/resolver-view/java/org/mulgara/resolver/view/ViewResolverUnitTest.java
trunk/src/jar/resolver/build.xml
trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java
trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java
trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java
trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionListQueryUnitTest.java
trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionUnitTest.java
trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java
trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java
trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java
trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransaction.java
trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java
trunk/src/jar/resolver/java/org/mulgara/resolver/XADatabaseSessionUnitTest.java
trunk/src/jar/server-beep/java/org/mulgara/server/beep/BEEPSession.java
trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSession.java
trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSessionWrapperSession.java
trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/SessionWrapperRemoteSession.java
Log:
refs #73
refs #85
Final merge of the JTA development into trunk.
Modified: trunk/.classpath
===================================================================
--- trunk/.classpath 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/.classpath 2008-03-18 09:32:14 UTC (rev 691)
@@ -86,9 +86,10 @@
<classpathentry kind="lib" path="lib/jsr173_07_ri.jar"/>
<classpathentry kind="lib" path="lib/jsr173_07_api.jar"/>
<classpathentry kind="lib" path="lib/js-1.5r3.jar"/>
- <classpathentry kind="lib" path="lib/jotm-1.5.3-patched.jar"/>
- <classpathentry kind="lib" path="lib/jotm_iiop_stubs-1.5.3.jar"/>
- <classpathentry kind="lib" path="lib/jotm_carol-1.5.3.jar"/>
+ <classpathentry kind="lib" path="lib/jotm-2.0.10.jar"/>
+ <classpathentry kind="lib" path="lib/jotm_jrmp_stubs-2.0.10.jar"/>
+ <classpathentry kind="lib" path="lib/carol-2.0.5.jar"/>
+ <classpathentry kind="lib" path="lib/howl-logger-0.1.11.jar"/>
<classpathentry kind="lib" path="lib/jl1.0.jar"/>
<classpathentry kind="lib" path="lib/jid3-0.34.jar"/>
<classpathentry kind="lib" path="lib/jboss-j2ee.jar"/>
Modified: trunk/LEGAL.txt
===================================================================
--- trunk/LEGAL.txt 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/LEGAL.txt 2008-03-18 09:32:14 UTC (rev 691)
@@ -202,15 +202,36 @@
http://www.gnu.org/copyleft/lesser.html
JOTM (Java Open Transaction Manager)
- JARs: jotm-1.5.3.jar (locally patched)
- jotm_carol-1.5.3.jar
- jotm_iiop_stubs-1.5.3.jar
- connector-1_5.jar (J2EE spec from Sun)
- jta-spec1_0_1.jar (J2EE spec from Sun)
+ JARs: jotm-2.0.10.jar
+ jotm_jrmp_stubs-2.0.10.jar
URL: http://jotm.objectweb.org/
LICENSE: BSD-style
http://jotm.objectweb.org/bsd_license.html
+
+ CAROL (Common Architecture for RMI ObjectWeb Layer)
+ JARs: carol-2.0.5.jar
+ URL: http://carol.objectweb.org/
+ LICENSE: LGPL
+ http://www.gnu.org/copyleft/lesser.html
+ HOWL (High-speed ObjectWeb Logger)
+ JARs: howl-logger-0.1.11.jar
+ URL: http://howl.objectweb.org/
+ LICENSE: Bull S.A.
+ http://howl.objectweb.org/license.html
+
+ Java Transaction API
+ JARs: jta-spec1_0_1.jar
+ URL: http://java.sun.com/products/jta
+ License: Common Development and Distribution License (CDDL) v1.0
+ http://www.sun.com/cddl/cddl.html
+
+ JavaEE Connector Architecture (JCA)
+ JARs: connector-1_5.jar (J2EE spec from Sun)
+ URL: http://java.sun.com/products/jta
+ License: Common Development and Distribution License (CDDL) v1.0
+ http://www.sun.com/cddl/cddl.html
+
JRDF
JARs: jrdf-0.3.4.3.jar
URL: http://mulgara.org/files/jrdf/
Modified: trunk/build.properties
===================================================================
--- trunk/build.properties 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/build.properties 2008-03-18 09:32:14 UTC (rev 691)
@@ -129,9 +129,10 @@
jsr.173.api.jar =jsr173_07_api.jar
jsr.173.ri.jar =jsr173_07_ri.jar
jta.jar =jta-spec1_0_1.jar
-jotm.carol.jar =jotm_carol-1.5.3.jar
-jotm.iiop.jar =jotm_iiop_stubs-1.5.3.jar
-jotm.jar =jotm-1.5.3-patched.jar
+carol.jar =carol-2.0.5.jar
+jotm.jrmp.jar =jotm_jrmp_stubs-2.0.10.jar
+jotm.jar =jotm-2.0.10.jar
+howl.jar =howl-logger-0.1.11.jar
l2fprod-common-sheet.jar =l2fprod-common-sheet.jar
log4j.jar =log4j-1.2.15.jar
lucene.jar =lucene-2.0.0.jar
Modified: trunk/build.sh
===================================================================
--- trunk/build.sh 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/build.sh 2008-03-18 09:32:14 UTC (rev 691)
@@ -116,7 +116,7 @@
fi
# Call Ant
-${JAVABIN} ${ARCH} -Xms64m -Xmx192m -Dant.home=${ANT_HOME} -DJAVAC=${JAVAC} \
+${JAVABIN} ${ARCH} -Xms64m -Xmx256m -Dant.home=${ANT_HOME} -DJAVAC=${JAVAC} \
-Darch.bits=${ARCH} \
-Ddir.base=${BASEDIR} \
-classpath "${CLASSPATH}" org.apache.tools.ant.Main \
Modified: trunk/build.xml
===================================================================
--- trunk/build.xml 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/build.xml 2008-03-18 09:32:14 UTC (rev 691)
@@ -494,7 +494,7 @@
lib/${date-utils.jar}, lib/${commons-logging.jar}, lib/beepcore-0.9.08.jar, lib/log4j-1.2.15.jar, lib/${lucene.jar}, lib/mail-1.3.jar,
lib/${jetty.jar}, lib/${jetty.plus.jar}, lib/${jasper.compiler.jar}, lib/${servlet.jar}, lib/jargs-0.2.jar,
lib/castor-0.9.3.9-xml.jar, lib/trove-1.0.2.jar, lib/${jrdf.jar}, lib/${saaj.jar},
- lib/${jakarta-oro.jar}, lib/jta-spec1_0_1.jar, lib/${jotm.jar}, lib/${jotm.carol.jar}, lib/${jotm.iiop.jar},
+ lib/${jakarta-oro.jar}, lib/jta-spec1_0_1.jar, lib/${jotm.jar}, lib/${jotm.jrmp.jar}, lib/${carol.jar}, lib/${howl.jar},
lib/connector-1_5.jar, lib/${httpclient.jar}, lib/${commons-codec.jar}"/>
<attribute name="Embedded-Main-Class" value="org.mulgara.server.EmbeddedMulgaraServer"/>
</manifest>
@@ -550,7 +550,7 @@
${lucene.jar}, ${mail.jar}, ${castor.jar}, ${trove.jar},
${date-utils.jar}, ${commons-logging.jar}, ${commons-httpclient.jar},
${commons-codec.jar}, ${emory-util.jar},
- ${jotm.jar}, ${jotm.carol.jar}, ${jotm.iiop.jar}, ${jta.jar},
+ ${jotm.jar}, ${jotm.jrmp.jar}, ${carol.jar}, ${howl.jar}, ${jta.jar},
${jsr.173.api.jar}, ${jsr.173.ri.jar}, ${jca.jar}, ${saaj.jar},
${axis.jar}, ${commons-discovery.jar}, ${jaxrpc.jar}, ${wsdl4j.jar},
${jena.jar}, ${antlr.jar}, ${jakarta-oro.jar}, {jid3.jar}"/>
@@ -1750,8 +1750,9 @@
<zipfileset src="${lib.dir}/${commons-codec.jar}" excludes="META-INF/**"/>
<zipfileset src="${lib.dir}/${emory-util.jar}" excludes="META-INF/**"/>
<zipfileset src="${lib.dir}/${jotm.jar}" excludes="META-INF/**"/>
- <zipfileset src="${lib.dir}/${jotm.carol.jar}" excludes="META-INF/**"/>
- <zipfileset src="${lib.dir}/${jotm.iiop.jar}" excludes="META-INF/**"/>
+ <zipfileset src="${lib.dir}/${jotm.jrmp.jar}" excludes="META-INF/**"/>
+ <zipfileset src="${lib.dir}/${carol.jar}" excludes="META-INF/**"/>
+ <zipfileset src="${lib.dir}/${howl.jar}" excludes="META-INF/**"/>
<zipfileset src="${lib.dir}/${jta.jar}" excludes="META-INF/**"/>
<zipfileset src="${lib.dir}/${jsr.173.api.jar}" excludes="META-INF/**"/>
<zipfileset src="${lib.dir}/${jsr.173.ri.jar}" excludes="META-INF/**"/>
Modified: trunk/common.xml
===================================================================
--- trunk/common.xml 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/common.xml 2008-03-18 09:32:14 UTC (rev 691)
@@ -152,7 +152,7 @@
jotm*.jar, commons-logging-*.jar, Quick4rt.jar,
commons-discovery-*.jar, commons-httpclient-*.jar,
commons-codec-*.jar, connector-*.jar, jaxrpc-*.jar,
- ${date-utils.jar}"
+ carol*.jar, howl-logger*.jar, ${date-utils.jar}"
excludes="jboss-j2ee.jar"/>
</path>
@@ -280,12 +280,76 @@
<batchtest todir="${junit.results.tmpdir}">
<fileset dir="${dir}/java" includes="**/*Test.java"
excludes="**/*AbstractTest.java, **/*LoadTest.java,
- **/*StressTest.java"/>
+ **/*StressTest.java, **/*StandaloneTest.java"/>
</batchtest>
</junit>
</target>
<!-- =================================================================== -->
+ <!-- Run JUnit regression tests requiring a standalone instance -->
+ <!-- -->
+ <!-- Required parameters: $classpath.id, $dir, $jar -->
+ <!-- =================================================================== -->
+ <target name="standalone-test"
+ depends="uri, dtd-jar, host.name, dist"
+ description="run regression tests requiring a standalone instance">
+
+ <copy file="${conf.dir}/log4j-template.xml"
+ tofile="${basedir}/log4j-conf.xml"/>
+
+ <mkdir dir="${junit.results.tmpdir}"/>
+
+ <!-- stop and start the server -->
+ <ant target="stop"/>
+ <ant target="start"/>
+
+ <junit fork="yes" haltonfailure="no" printsummary="on"
+ jvm="${java.home}/bin/java" dir="${basedir}">
+
+ <jvmarg value="${arch.bits}"/>
+ <jvmarg value="-ea"/>
+ <jvmarg value="${jvm.args}"/>
+
+ <sysproperty key="java.io.tmpdir" value="${tmp.dir}"/>
+ <sysproperty key="test.dir" value="${test.dir}"/>
+ <sysproperty key="admin" value=""/>
+ <sysproperty key="itql.command.log" value="${tmp.dir}/itql.log"/>
+ <sysproperty key="org.mulgara.xml.ResourceDocumentBuilderFactory"
+ value="org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"/>
+ <sysproperty key="java.naming.factory.initial"
+ value="com.sun.jndi.rmi.registry.RegistryContextFactory"/>
+ <sysproperty key="java.naming.provider.url" value="rmi://localhost"/>
+ <sysproperty key="java.rmi.server.codebase"
+ value="${bin.uri}/${rmi.base.jar}"/>
+ <sysproperty key="java.security.manager" value=""/>
+ <sysproperty key="java.security.policy"
+ value="${basedir}/conf/mulgara-test.policy"/>
+ <sysproperty key="host.name" value="${host.name}"/>
+
+ <!-- The cvs.root property allows tests to find CVS'ed test data -->
+ <sysproperty key="cvs.root" value="${basedir}"/>
+ <sysproperty key="mulgara.jar" value="${mulgara.jar}"/>
+ <sysproperty key="log4j.configuration" value="${baseuri}/log4j-conf.xml"/>
+ <sysproperty key="basedir" value="${basedir}"/>
+ <sysproperty key="org.mulgara.test" value="${org.mulgara.test}"/>
+
+ <classpath>
+ <fileset dir="${dist.dir}" includes="${mulgara.jar}"/>
+ </classpath>
+
+ <formatter type="xml"/>
+
+ <batchtest todir="${junit.results.tmpdir}">
+ <fileset dir="${dir}/java" includes="**/*StandaloneTest.java"/>
+ </batchtest>
+ </junit>
+
+ <!-- stop the server -->
+ <ant target="stop"/>
+
+ </target>
+
+ <!-- =================================================================== -->
<!-- Run JUnit load tests on a JAR component -->
<!-- -->
<!-- Required parameters: $classpath.id, $dir, $jar -->
Copied: trunk/docs/site-src/design/JTAInterface.txt (from rev 690, branches/mgr-73/docs/site-src/design/JTAInterface.txt)
===================================================================
--- trunk/docs/site-src/design/JTAInterface.txt (rev 0)
+++ trunk/docs/site-src/design/JTAInterface.txt 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,271 @@
+The Mulgara JTA Interface
+
+The Existing Interface
+
+Mulgara originally provided two transactional interfaces, implicit and explicit.
+This mediated via the Session object, and the method used to select between them
+is called setAutoCommit(). When auto-commit is true, all operations on the
+Session use the implicit interface. Setting auto-commit false initiates an
+explicit (or user-demarcated) transaction. Irrespective of any commit/rollback,
+once auto-commit is false all transactions are user-demarcated until it is set
+to true again. While auto-commit is true every operation on the Session will
+initiate a new transaction which will be finalised (either commit or rollback)
+before the operation returns.
+
+All transactions initiated via the explicit interface are read-write, whereas
+only those operations that perform updates initiate implicit read-write
+transactions. Naturally both interfaces use the same underlying transactional
+mechanisms within Mulgara, including a single shared write-lock that only
+permits a single read-write transaction to proceed at a time.
+
+Explicit transactions are controlled via three methods on the Session interface.
+ - Session::setAutoCommit() allows the developer to select which transactional
+ interface to use, and to initiate a transaction (this does conflate two
+ concepts, but todate that has not proven to be a problem).
+ - Session::commit() commits the current transaction an initiates a new
+ transaction without dropping the write-lock.
+ - Session::rollback() rollbacks the current transaction an initiates a new
+ transaction without dropping the write-lock.
+There are two other ways for a user-demarcated transaction to be terminated.
+ - If an exception is thrown from an operation on the session, or on a query
+ result associated with the transaction, the transaction is rolled back and
+ the write-lock is dropped.
+ - If the transaction timeout expires then the transaction is rolled back and
+ the write-lock is dropped.
+In both cases the session remains in explicit transaction control however as
+all operations start by attempting to activate the transaction, any attempt to
+perform any non-transaction control operation on the session results in an
+exception.
+
+To ensure full transaction isolation/serialisability each answer returned from a
+query holds a reference to the transaction/phase it was performed against. This
+means that the results of queries performed within an implicit transaction
+remain valid until such a time as either the result or the originating session
+itself is closed. However due to this reliance on the originating transaction,
+query results obtained from an explicit transaction do not out live it, and are
+invalidated as soon as the transaction is terminated.
+
+This approach greatly simplifies the normal case of an application that is
+performing an atomic insert, delete, or query. In this case the application
+developer can ignore transactions altogether and the implict transaction control
+will 'do the right thing'. For more complex applications, the explicit
+transation controls available on session are sufficient for almost all uses of
+mulgara as a standalone store.
+
+Motivation for a new interface
+
+There are however two gaps in the original transaction interface.
+
+The first is that it is impossible to perform a series of transactionally
+consistant queries except by grouping them into a single call to
+Session::query(List); or by first initiating an explicit transaction in order to
+obtain the write-lock, preventing any potentially inconsistent updates.
+Naturally the former is not always possible, and the latter is undesirable,
+especially as mulgara is a multiversion datastore and it therefore should be
+possible to obtain a handle to a given version (phase) and run multiple queries
+against it.
+
+The second is that the interface provides no access to the two-phase commit
+protocol used by mulgara internally. This isn't a problem when using mulgara as
+a standalone server, however it does make it impossible to incorporate mulgara
+in a distributed transaction with other stores or databases safely.
+
+The Java standard for providing access to a two-phase commit protocol is the
+"Java Transaction API" (JTA). A java transaction (and slight simplification) of
+the Object Management Group (OMG)'s "Transaction Service Specification" (OTS), which
+is the transaction management specification for CORBA. The OTS is itself an
+object-oriented mapping of the "DTP/XA Specification" (XA-spec)from X/Open (now The Open
+Group). The relevant versions of the various specifications are:
+ - JTA : Java Transaction API (JTA), Version 1.1, 2002
+ - OTS : Transaction Service Specification, Version 1.2, 2001
+ - XA-Spec : Distributed Transaction Processing: The XA Specification,
+ XO/CAE/91/300, 1991
+The XA-Spec relies for context on the "DTP/Reference Model" (XA-model), 1991;
+although the mulgara JTA implementation was guided with reference to the more
+recent version 3, 1996.
+ - XA-model : Distribute Transaction Processing: Reference Model Version 3,
+ G504, 1996
+
+While the primary requirement for JTA support is the need to provide a two-phase
+commit interface, the opportunity was also taken to provide explicit control of
+read-only transactions which solves the problem of supporting multiple
+consistent reads as discussed above.
+
+Limitations of the JTA interface
+
+The write-lock is not a consequence of the existing interface, but rather a
+property of the underlying mulgara XA1 store implementation. As a result the
+use of JTA does not affect the serialisation of write transactions. It remains
+the case that any attempt to initiate a write-transaction will block until such
+a time as the write-lock becomes available. The current JTA implementation
+within mulgara does not provide any timeout on this wait, so application
+developers should be aware that calls to start a mulgara transaction may block
+for an arbitary time.
+
+On restart mulgara automatically and releases (garbage collects) any phases
+(versions) other than the most recently committed. This startup protocol is
+done without any journal reruns resulting in an instantaneous restart. On the
+other hand this protocol does mean that mulgara discards any prepared
+transactions on restart, effectively performing a heuristic rollback. As
+mulgara makes no distinction between prepared and unprepared transactions on
+restart, mulgara does not persist the prepared status of a transaction. This
+means that the mulgara JTA implementation cannot participate in the JTA recovery
+protocol. As a result on restart users should consider all transactions to be
+Heuristically Completed (state S5 in XA-spec 6.3), with status XA_HEURRB in the
+case of a restart prior to a call to commit, and status XA_HEURHAZ on a restart
+during a commit call. It is apropos here to recall that XA_HEURMIX is only
+possible if an update operation is performed on an external datastore via a
+resolver (at the moment only lucene supports this); atomicity is guaranteed
+with respect to the mulgara rdf-store itself.
+
+The JTA interface is a Java interface that is expected to operate transparently
+over a network. As with Mulgara's session interface, the JTA interface uses RMI
+to provide network transparency. This should not be a problem as the RMI based
+JTA implementation can only be obtained via the RMI based session
+implementation, however any interruption to RMI operation will affect
+transaction control.
+
+Accessing the JTA interface
+
+The fundamental requirement for supporting JTA is providing a way of obtaining
+an XAResource. JTA requires that each XAResource be associated with a "Resource
+Manager Instance". When mapping this to Mulgara, and taking into consideration
+the code in the JTA-spec (3.4.9), the Session object was identified as best
+fitting JTA's use of the term RM instance. The JTA interface therefore adds two
+new methods to Session:
+ Session::getXAResource()
+ Session::getReadOnlyXAResource()
+Both return an XAResource object, associated with the session. All transactions
+initiated via a call to XAResource::start() will be read-write or read-only
+depending on if the XAResource was obtained via getXAResource or
+getReadOnlyXAResource respectively.
+
+The preexisting interface manages the transaction state transitions internally,
+as compared to the JTA interface which exposes these transitions to the external
+control of an external transaction manager. Consequently within mulgara
+transactions created via the JTA interface are referred to as "external
+transactions", those by the older implicit and explicit interface as "internal
+transactions". While JTA 3.4.7 encourages supporting both Local (internal) and
+Global (external) transactions through the same "connection", supporting this
+would drastically increase the number of operation combinations that would need
+to be tested to ensure confidence in the transaction logic. Therefore the
+current mulgara JTA implementation does not support the use of both internal and
+external transactions on the same session. To select which interface is active
+on a given session the developer should simply use it. The first use of a given
+interface (internal or external) disables the other. This ensures full
+backwards compatibility with existing code while simplifying the process of
+testing and building confidence in the new interface.
+
+Using the JTA interface
+
+It is worth mentioning that none of the underlying transactional machinery has
+changed, consequently all Answer objects still reference their originating
+transaction, and all transactions ultimately must map to a specific phase on the
+store. As a result it is important to keep in mind that once a JTA mediated
+transaction is terminated via the XAResource, the reference to the phase is
+released and all outstanding Answer objects are invalidated. In this the JTA
+interface works very similarly to the explict internal interface.
+
+JTA also recommends supporting multiple outstanding transactions on a single RM
+instance. The mulgara JTA implementation provides full support for this,
+however developers attempting to use this functionality *must* be careful to
+observe the restrictions imposed by the JTA standard. JTA provides support for
+concurrent transactions on a single resource adaptor via calls to
+XAResource::suspend() and XAResource::resume(). These transition the specified
+transaction from Associated to Suspended states (see JTA-spec 3.4.4, although
+XA-spec Ch.6 is more complete). JTA requires (as does the mulgara
+implementation) that at most one transaction be associated with the
+transactional resource at any given time.
+
+The following example of concurrent use of JTA transactions on a single session
+is excerpted from org.mulgara.resolver.JotmTransactionStandaloneUnitTest.java
+
+{
+ txManager.begin();
+
+ Session session = sessionFactory.newSession();
+ XAResource roResource = session.getReadOnlyXAResource();
+ XAResource rwResource = session.getXAResource();
+
+ /*
+ * Note: JTA requires that you initiate the global transaction (via mgr::begin()
+ * above), before you enlist a resource.
+ * The call to enlistResource for an XAResource for the first time in a given
+ * global transaction will cause the Transaction Manager to call xa::start() on
+ * the XAResource, initiating a mulgara transaction.
+ * As we are using an XAResource obtained via xa::getXAResource(), this
+ * transaction will be a read-write transaction - note this does mean that
+ * this call could block waiting on the write-lock availability.
+ */
+ txManager.getTransaction().enlistResource(rwResource);
+ session.createModel(...);
+ Transaction tx1 = txManager.suspend();
+ /*
+ * Note: Without the call to mgr::suspend, subsequent calls to enlistResource will
+ * detect that there is an existing transaction and result in a call to
+ * xa::start(TM_JOIN), which is either a no-op (if a read-only 'joins' a
+ * read-write transaction) or an exception (if a read-write attempts to 'join'
+ * a read-only transaction).
+ * With the call to mgr::suspend() the transaction is temporarally
+ * disassociated from this thread, and from this session. A Consequent call to
+ * mgr::begin() will result in the transaction manager initiating a new
+ * transaction, and an ensuing call to xa::enlistResource a new mulgara
+ * transaction.
+ * It is important to realise that all operations on a session object are
+ * evaluated with respect to the currently associated transaction. If all
+ * transactions are suspended or completed then operations will result in an
+ * exception.
+ */
+
+ txManager.begin();
+ txManager.getTransaction().enlistResource(roResource);
+ Answer answer = session.query(new Query(...));
+ Transaction tx2 = txManager.suspend();
+
+ /*
+ * Answers carry their own association with the mulgara transaction, so the
+ * transaction does not need to be reassociated with the originating session
+ * to use an Answer object. This does run counter to assumptions of the JTA
+ * specification, however as the JTA specification assumes a traditional
+ * relational database, many of the capabilities of multiversion datastores
+ * (such as mulgara) flexibility is required in adapting multiversion
+ * transactions to JTA.
+ */
+ answer.beforeFirst();
+ while (answer.next()) {
+ ...
+ }
+
+ txManager.resume(tx1);
+ session.insert(...);
+ tx1 = txManager.suspend();
+ answer.close();
+ txManager.resume(tx1);
+ txManager.commit();
+
+ /*
+ * Note the suport for user-demarcated concurrent read-only transactions.
+ */
+ txManager.begin();
+ txManager.getTransaction().enlistResource(roResource);
+ Answer answer2 = session.query(new Query(...));
+ Transaction tx3 = txManager.suspend();
+
+ // use answer2.
+ answer2.close();
+
+ /*
+ * Note the call to tx3.commit() is performed on a suspended transaction. As
+ * tx3 is a handle to the transaction it does not need to be reassociated with
+ * the current thread as is required if it is going to be committed via the
+ * transaction manager. This flexibility is mandated by JTA.
+ */
+ txManager.begin();
+ txManager.getTransaction().enlistResource(rwResource);
+ session.removeModel(...);
+ txManager.commit();
+ txManager.resume(tx2);
+ txManager.commit();
+ tx3.commit();
+ session.close();
+}
Modified: trunk/jxdata/iTQL/data_types/queryResult34.txt
===================================================================
--- trunk/jxdata/iTQL/data_types/queryResult34.txt 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/jxdata/iTQL/data_types/queryResult34.txt 2008-03-18 09:32:14 UTC (rev 691)
@@ -1,4 +1,4 @@
-ItqlInterpreter error - Could not commit insert
+ItqlInterpreter error - Could not commit modify
Caused by: (QueryException) org.mulgara.query.MulgaraTransactionException: Transaction rollback triggered
Caused by: (QueryException) org.mulgara.resolver.spi.ResolverException: Unable to read input statements
Caused by: (QueryException) org.mulgara.query.TuplesException: Failed to localize node
Modified: trunk/jxdata/iTQL/standard_queries/queryResult17.txt
===================================================================
--- trunk/jxdata/iTQL/standard_queries/queryResult17.txt 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/jxdata/iTQL/standard_queries/queryResult17.txt 2008-03-18 09:32:14 UTC (rev 691)
@@ -1,3 +1,3 @@
-ItqlInterpreter error - Could not commit insert
+ItqlInterpreter error - Could not commit modify
Caused by: (QueryException) org.mulgara.query.MulgaraTransactionException: Transaction rollback triggered
Caused by: (QueryException) rmi://localhost/server1#nomodelexistswiththisname is not a Model
Copied: trunk/lib/carol-2.0.5.jar (from rev 690, branches/mgr-73/lib/carol-2.0.5.jar)
===================================================================
(Binary files differ)
Copied: trunk/lib/howl-logger-0.1.11.jar (from rev 690, branches/mgr-73/lib/howl-logger-0.1.11.jar)
===================================================================
(Binary files differ)
Deleted: trunk/lib/jotm-1.5.3-patched.jar
===================================================================
(Binary files differ)
Copied: trunk/lib/jotm-2.0.10.jar (from rev 690, branches/mgr-73/lib/jotm-2.0.10.jar)
===================================================================
(Binary files differ)
Deleted: trunk/lib/jotm_carol-1.5.3.jar
===================================================================
(Binary files differ)
Deleted: trunk/lib/jotm_iiop_stubs-1.5.3.jar
===================================================================
(Binary files differ)
Copied: trunk/lib/jotm_jrmp_stubs-2.0.10.jar (from rev 690, branches/mgr-73/lib/jotm_jrmp_stubs-2.0.10.jar)
===================================================================
(Binary files differ)
Modified: trunk/src/jar/itql/java/org/mulgara/itql/TqlInterpreter.java
===================================================================
--- trunk/src/jar/itql/java/org/mulgara/itql/TqlInterpreter.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/itql/java/org/mulgara/itql/TqlInterpreter.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -814,7 +814,7 @@
// build the variable list: collection of Variable, ConstantValue, Count, Subquery
if (logger.isDebugEnabled()) logger.debug("Building query variable list from " + variables);
- List<Object> variableList = this.buildVariableList(variables);
+ List<SelectElement> variableList = this.buildVariableList(variables);
if (logger.isDebugEnabled()) logger.debug("Built variable list " + variableList);
// get the model expression from the parser
@@ -999,11 +999,11 @@
* into a list of {@link org.mulgara.query.Variable}s
*/
@SuppressWarnings("unchecked")
- List<Object> buildVariableList(LinkedList<PElement> rawVariableList) throws
+ List<SelectElement> buildVariableList(LinkedList<PElement> rawVariableList) throws
QueryException, URISyntaxException {
// Empty variable list.
- if (rawVariableList == null) return (List<Object>)Collections.EMPTY_LIST;
+ if (rawVariableList == null) return Collections.emptyList();
// validate rawVariableList parameter
if (rawVariableList.size() == 0) throw new IllegalArgumentException("Empty \"rawVariableList\" parameter");
@@ -1019,7 +1019,7 @@
for (PElement element: rawVariableList) element.apply((Switch)variableBuilder);
// Get the variable list
- List<Object> variableList = variableBuilder.getVariableList();
+ List<SelectElement> variableList = variableBuilder.getVariableList();
// make sure that we return a list with something in it
if (variableList.size() == 0) {
Modified: trunk/src/jar/itql/java/org/mulgara/itql/VariableBuilder.java
===================================================================
--- trunk/src/jar/itql/java/org/mulgara/itql/VariableBuilder.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/itql/java/org/mulgara/itql/VariableBuilder.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -78,7 +78,7 @@
* The list of variables - these are mixed object types:
* ConstantValue, Variable, Count, SubQuery
*/
- private List<Object> variableList;
+ private List<SelectElement> variableList;
/** The TQL interpreter */
private SableCCInterpreter interpreter;
@@ -90,7 +90,7 @@
* @param newInterpreter the interpreter to use.
*/
public VariableBuilder(SableCCInterpreter newInterpreter, VariableFactory newVariableFactory) {
- variableList = new ArrayList<Object>();
+ variableList = new ArrayList<SelectElement>();
interpreter = newInterpreter;
variableFactory = newVariableFactory;
}
@@ -182,9 +182,9 @@
* @throws URISyntaxException if the variable contains a resource whose
* text violates <a href="http://www.isi.edu/in-notes/rfc2396.txt">RFC?2396</a>
*/
- public List<Object> getVariableList() throws QueryException, URISyntaxException {
+ public List<SelectElement> getVariableList() throws QueryException, URISyntaxException {
try {
- List<Object> tmpVariableList = new ArrayList<Object>(variableList);
+ List<SelectElement> tmpVariableList = new ArrayList<SelectElement>(variableList);
if (uriException != null) throw uriException;
else if (queryException != null) throw queryException;
Modified: trunk/src/jar/krule/java/org/mulgara/krule/QueryStruct.java
===================================================================
--- trunk/src/jar/krule/java/org/mulgara/krule/QueryStruct.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/krule/java/org/mulgara/krule/QueryStruct.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -48,6 +48,7 @@
import org.mulgara.query.ModelUnion;
import org.mulgara.query.Order;
import org.mulgara.query.Query;
+import org.mulgara.query.SelectElement;
import org.mulgara.query.UnconstrainedAnswer;
import org.mulgara.query.Variable;
import org.mulgara.query.VariableFactory;
@@ -76,7 +77,7 @@
private ConstraintElement[] select = new ConstraintElement[3];
/** List of elements which are variables, or ConstantValues. */
- private List<Object> variables;
+ private List<SelectElement> variables;
/** The model expresison for the query. */
private ModelExpression models;
@@ -112,7 +113,7 @@
VariableFactory variableFactory = new VariableFactoryImpl();
// set up a list of variables
- variables = new ArrayList<Object>();
+ variables = new ArrayList<SelectElement>();
// convert the parameters to usable objects
for (int i = 0; i < 3; i++) {
@@ -129,7 +130,7 @@
// get the variable
select[i] = (Variable)varReferences.get(element);
- variables.add(select[i]);
+ variables.add((Variable)select[i]);
} else if (types[i].equals(KruleLoader.LITERAL)) {
Modified: trunk/src/jar/query/java/org/mulgara/query/AggregateFunction.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/AggregateFunction.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/query/java/org/mulgara/query/AggregateFunction.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -49,7 +49,7 @@
*
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
-public abstract class AggregateFunction implements Serializable {
+public abstract class AggregateFunction implements SelectElement, Serializable {
/**
* Allow newer compiled version of the stub to operate when changes
Modified: trunk/src/jar/query/java/org/mulgara/query/ConstantValue.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/ConstantValue.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/query/java/org/mulgara/query/ConstantValue.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -48,7 +48,7 @@
*
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
-public class ConstantValue implements Serializable {
+public class ConstantValue implements SelectElement, Serializable {
/**
* Allow newer compiled version of the stub to operate when changes
Modified: trunk/src/jar/query/java/org/mulgara/query/Query.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/Query.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/query/java/org/mulgara/query/Query.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
- * Contributor(s): N/A.
+ * Contributor(s):
+ * Move to java-generics copyright Netymon Pty Ltd
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
@@ -72,13 +73,13 @@
* that there is no <code>select</code> clause and that no projection will be
* performed.
*/
- private List<Object> variableList;
+ private List<SelectElement> variableList;
/**
* Mutable version of the variable list. This isn't exposed via {@link
* #getVariableList} the way {@link #variableList} is.
*/
- private List<Object> mutableVariableList;
+ private List<SelectElement> mutableVariableList;
/** The model expression. It corresponds to the <code>from</code> clause. */
private ModelExpression modelExpression;
@@ -142,7 +143,7 @@
* <var>constraintExpression</var>, <var>orderList<var> or
* <var>answer</var> are <code>null</code>
*/
- public Query(List<Object> variableList, ModelExpression modelExpression,
+ public Query(List<? extends SelectElement> variableList, ModelExpression modelExpression,
ConstraintExpression constraintExpression,
ConstraintHaving havingExpression, List<Order> orderList, Integer limit,
int offset, Answer answer) {
@@ -177,7 +178,7 @@
}
// Initialize fields
- this.mutableVariableList = (variableList == null) ? null : new ArrayList<Object>(variableList);
+ this.mutableVariableList = (variableList == null) ? null : new ArrayList<SelectElement>(variableList);
this.variableList = (variableList == null) ? null : Collections.unmodifiableList(mutableVariableList);
this.modelExpression = modelExpression;
this.constraintExpression = constraintExpression;
@@ -233,10 +234,8 @@
cloned.mutableVariableList = null;
cloned.variableList = null;
} else {
- cloned.variableList = new ArrayList<Object>();
- Iterator<Object> i = variableList.iterator();
- while (i.hasNext()) {
- Object o = i.next();
+ cloned.variableList = new ArrayList<SelectElement>();
+ for (SelectElement o : variableList) {
if (o instanceof Subquery) {
Subquery s = (Subquery)o;
cloned.variableList.add(new Subquery(s.getVariable(), (Query)s.getQuery().clone()));
@@ -270,7 +269,7 @@
* @return a {@link List} containing one or more {@link Variable}s, {@link ContantValue}s,
* {@link Count}s or {@link Subquery}
*/
- public List<Object> getVariableList() {
+ public List<SelectElement> getVariableList() {
return variableList;
}
@@ -407,8 +406,10 @@
answer = null;
if (mutableVariableList != null) {
- for (Object v: mutableVariableList) {
- if (v instanceof AggregateFunction) ((AggregateFunction)v).getQuery().close();
+ for (SelectElement v: mutableVariableList) {
+ if (v instanceof AggregateFunction) {
+ ((AggregateFunction)v).getQuery().close();
+ }
}
}
}
Modified: trunk/src/jar/query/java/org/mulgara/query/QueryUnitTest.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/QueryUnitTest.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/query/java/org/mulgara/query/QueryUnitTest.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -45,18 +45,8 @@
* Purpose: Test case for {@link Query}.
*
* @created 2001-10-23
- *
* @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a>
- *
- * @version $Revision: 1.8 $
- *
- * @modified $Date: 2005/01/05 04:58:20 $ by $Author: newmana $
- *
- * @maintenanceAuthor $Author: newmana $
- *
- * @copyright ©2001-2004
- * <a href="http://www.pisoftware.com/">Plugged In Software Pty Ltd</a>
- *
+ * @copyright ©2001-2004 <a href="http://www.pisoftware.com/">Plugged In Software Pty Ltd</a>
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
public class QueryUnitTest extends TestCase {
@@ -84,7 +74,6 @@
* @return The test suite
*/
public static Test suite() {
-
TestSuite suite = new TestSuite();
suite.addTest(new QueryUnitTest("test1Equals"));
@@ -100,7 +89,6 @@
* @param args the command line arguments
*/
public static void main(String[] args) {
-
junit.textui.TestRunner.run(suite());
}
@@ -111,9 +99,8 @@
*/
@SuppressWarnings("unchecked")
public void setUp() throws Exception {
-
query = new Query(
- Arrays.asList(new Object[] {new Variable("x")}), // variable list
+ Arrays.asList(new SelectElement[] { new Variable("x") }), // variable list
new ModelResource(new URI("x:m")), // model expression
new ConstraintImpl(new Variable("x"), // constraint expression
new URIReferenceImpl(new URI("x:p")),
@@ -135,7 +122,6 @@
* @throws Exception if query fails when it should have succeeded
*/
public void testClone() throws Exception {
-
Query copied = (Query) query.clone();
assertTrue(copied != query);
assertEquals(copied, query);
@@ -147,9 +133,7 @@
* @throws Exception if query fails when it should have succeeded
*/
public void test1Equals() throws Exception {
-
Query query2 = query;
-
assertTrue(query.equals(query2));
}
@@ -160,16 +144,15 @@
*/
@SuppressWarnings("unchecked")
public void test2Equals() throws Exception {
-
// Compose test instances
Query query2 = new Query(
- Arrays.asList(new Object[] {new Variable("x")}), // variable list
+ Arrays.asList(new SelectElement[] { new Variable("x") }), // variable list
new ModelResource(new URI("x:m")), // model expression
new ConstraintImpl(new Variable("x"), // constraint expression
new URIReferenceImpl(new URI("x:p")),
new LiteralImpl("o")),
null, // no having
- (List<Order>)Collections.EMPTY_LIST, // no ordering
+ (List<Order>)Collections.EMPTY_LIST, // no ordering
null, // no limit
0, // zero offset
new UnconstrainedAnswer());
@@ -186,7 +169,6 @@
* @throws Exception if query fails when it should have succeeded
*/
public void test3Equals() throws Exception {
-
assertTrue(!query.equals(null));
}
}
Copied: trunk/src/jar/query/java/org/mulgara/query/SelectElement.java (from rev 690, branches/mgr-73/src/jar/query/java/org/mulgara/query/SelectElement.java)
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/SelectElement.java (rev 0)
+++ trunk/src/jar/query/java/org/mulgara/query/SelectElement.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2008 The Topaz Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+package org.mulgara.query;
+
+import java.io.Serializable;
+
+/**
+ * Marker interface to identify elements legally permitted within the
+ * select-clause of an itql query.
+ *
+ * @created 2008-01-14
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ * @copyright ©2008 <a href="http://www.topazproject.org/">The Topaz Foundation</a>
+ * @licence Apache License v2.0
+ */
+public interface SelectElement extends Serializable, Cloneable { }
Modified: trunk/src/jar/query/java/org/mulgara/query/Variable.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/query/Variable.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/query/java/org/mulgara/query/Variable.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -45,7 +45,7 @@
*
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
-public class Variable implements ConstraintElement {
+public class Variable implements SelectElement, ConstraintElement {
/**
* Allow newer compiled version of the stub to operate when changes
Copied: trunk/src/jar/query/java/org/mulgara/server/ResourceManagerInstanceAdaptor.java (from rev 690, branches/mgr-73/src/jar/query/java/org/mulgara/server/ResourceManagerInstanceAdaptor.java)
===================================================================
--- trunk/src/jar/query/java/org/mulgara/server/ResourceManagerInstanceAdaptor.java (rev 0)
+++ trunk/src/jar/query/java/org/mulgara/server/ResourceManagerInstanceAdaptor.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2008 The Topaz Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+package org.mulgara.server;
+
+
+// Java 2 Standard Packages
+import java.net.*;
+import java.util.*;
+import java.io.*;
+import javax.transaction.xa.XAResource;
+
+/**
+ * Interface to identify a Resource Manager through an XAResource.
+ *
+ * @created 2008-01-14
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ * @copyright ©2008 <a href="http://www.topazproject.org/">The Topaz Foundation</a>
+ * @licence Apache License v2.0
+ */
+public interface ResourceManagerInstanceAdaptor {
+ /**
+ * Returns an object that globally identifies a ResourceManagerInstance.
+ * Returned object must override equals/hashcode to compare equal by value.
+ */
+ public Serializable getRMId();
+}
Modified: trunk/src/jar/query/java/org/mulgara/server/Session.java
===================================================================
--- trunk/src/jar/query/java/org/mulgara/server/Session.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/query/java/org/mulgara/server/Session.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
- * Contributor(s): N/A.
+ * Contributor(s):
+ * XAResource addition copyright 2008 The Topaz Foundation
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
@@ -32,6 +33,7 @@
import java.net.*;
import java.util.*;
import java.io.*;
+import javax.transaction.xa.XAResource;
// Locally written packages
import org.jrdf.graph.Triple;
@@ -79,7 +81,7 @@
* @param statements The Set of statements to insert into the model.
* @throws QueryException if the insert cannot be completed.
*/
- public void insert(URI modelURI, Set<Triple> statements) throws QueryException;
+ public void insert(URI modelURI, Set<? extends Triple> statements) throws QueryException;
/**
* Insert statements from the results of a query into another model.
@@ -97,7 +99,7 @@
* @param statements The Set of statements to delete from the model.
* @throws QueryException if the deletion cannot be completed.
*/
- public void delete(URI modelURI, Set<Triple> statements) throws QueryException;
+ public void delete(URI modelURI, Set<? extends Triple> statements) throws QueryException;
/**
* Delete statements from a model using the results of query.
@@ -304,4 +306,30 @@
*/
public void login(URI securityDomain, String username, char[] password);
+ /**
+ * Obtain an XAResource for this Session.
+ *
+ * Use of this method is incompatible with any use of implicit or internally
+ * mediated transactions with this Session.
+ * Transactions initiated from the XAResource returned by the read-only
+ * version of this method will be read-only.
+ */
+ public XAResource getXAResource() throws QueryException;
+ public XAResource getReadOnlyXAResource() throws QueryException;
+
+ /**
+ * This class is just a devious way to get static initialization for the
+ * {@link Session} interface.
+ */
+ abstract class ConstantFactory {
+
+ static URI getMulgaraModelURI() {
+ try {
+ return new URI(Mulgara.NAMESPACE + "Model");
+ }
+ catch (URISyntaxException e) {
+ throw new Error("Bad hardcoded URI");
+ }
+ }
+ }
}
Modified: trunk/src/jar/resolver/build.xml
===================================================================
--- trunk/src/jar/resolver/build.xml 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/build.xml 2008-03-18 09:32:14 UTC (rev 691)
@@ -32,6 +32,7 @@
<fileset file="${resolver-jar.dist.dir}/${resolver-jar.jar}"/>
<fileset file="${resolver-url.dist.dir}/${resolver-url.jar}"/>
<fileset file="${content-rdfxml.dist.dir}/${content-rdfxml.jar}"/>
+ <fileset file="${driver.dist.dir}/${driver.jar}"/>
</path>
<path id="resolver-test-classpath">
@@ -81,7 +82,7 @@
depends="-resolver-prepare, resolver-spi-jar, rules-jar,
resolver-http-jar, resolver-file-jar, resolver-url-jar,
resolver-jar-jar, content-rdfxml-jar, content-rdfxml-jar,
- client-jrdf-jar, jrdf-jar, resolver-xsd-jar"
+ client-jrdf-jar, jrdf-jar, resolver-xsd-jar, driver-jar"
description="Compiles all resolver related files included generated
source code">
@@ -128,14 +129,21 @@
resolver-jar, resolver-memory-jar, resolver-xsd-jar,
store-nodepool-xa-jar, store-stringpool-xa-jar, resolver-store-jar,
resolver-url-jar, store-stringpool-memory-jar, tuples-hybrid-jar,
- util-jar, util-xa-jar">
+ util-jar, util-xa-jar, driver-jar">
+
<antcall target="component-test">
<param name="classpath.id" value="resolver-test-classpath"/>
<param name="dir" value="${resolver.src.dir}"/>
<param name="jar" value="${resolver.jar}"/>
</antcall>
+
+ <antcall target="standalone-test">
+ <param name="classpath.id" value="resolver-test-classpath"/>
+ <param name="dir" value="${resolver.src.dir}"/>
+ <param name="jar" value="${resolver.jar}"/>
+ </antcall>
</target>
<target name="resolver-javadoc"
Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/AdvDatabaseSessionUnitTest.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -63,6 +63,7 @@
import org.mulgara.query.Order;
import org.mulgara.query.Query;
import org.mulgara.query.QueryException;
+import org.mulgara.query.SelectElement;
import org.mulgara.query.Subquery;
import org.mulgara.query.TuplesException;
import org.mulgara.query.UnconstrainedAnswer;
@@ -243,7 +244,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -301,7 +302,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -361,10 +362,10 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(new Subquery(new Variable("k0"), new Query(
- Collections.singletonList((Object)objectVariable),
+ Collections.singletonList(objectVariable),
new ModelResource(modelURI), // FROM
new ConstraintImpl(subjectVariable, // WHERE
predicateVariable,
@@ -436,10 +437,10 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(new Subquery(new Variable("k0"), new Query(
- Collections.singletonList((Object)objectVariable),
+ Collections.singletonList(objectVariable),
new ModelResource(modelURI), // FROM
new ConstraintImpl(subjectVariable, // WHERE
predicateVariable,
@@ -525,7 +526,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -612,7 +613,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -688,7 +689,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -716,7 +717,7 @@
session1.setAutoCommit(true);
- selectList = new ArrayList<Object>(3);
+ selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -785,7 +786,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -814,7 +815,7 @@
session1.rollback();
session1.setAutoCommit(true);
- selectList = new ArrayList<Object>(3);
+ selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -869,7 +870,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -897,7 +898,7 @@
session1.commit();
- selectList = new ArrayList<Object>(3);
+ selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -940,7 +941,7 @@
session1.removeModel(model3URI);
session1.createModel(model3URI, null);
- selectList = new ArrayList<Object>(3);
+ selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -982,7 +983,7 @@
session1.setAutoCommit(true);
- selectList = new ArrayList<Object>(3);
+ selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -1035,7 +1036,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -1158,7 +1159,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -1220,7 +1221,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -1342,7 +1343,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -1398,7 +1399,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -1482,7 +1483,7 @@
Variable varB = new Variable("b");
Variable varT = new Variable("t");
- List<Object> selectList = new ArrayList<Object>(2);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(2);
selectList.add(varA);
selectList.add(varT);
Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/BasicDatabaseSessionUnitTest.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -187,7 +187,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
@@ -276,7 +276,7 @@
Variable predicateVariable = new Variable("predicate");
Variable objectVariable = new Variable("object");
- List<Object> selectList = new ArrayList<Object>(3);
+ List<SelectElement> selectList = new ArrayList<SelectElement>(3);
selectList.add(subjectVariable);
selectList.add(predicateVariable);
selectList.add(objectVariable);
Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSession.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -16,9 +16,11 @@
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
- * Contributor(s): N/A.
+ * Contributor(s):
* SymbolicTransformation refactor contributed by Netymon Pty Ltd on behalf of
* The Australian Commonwealth Government under contract 4500507038.
+ * External XAResource contributed by Netymon Pty Ltd on behalf of Topaz
+ * Foundation under contract.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
@@ -34,6 +36,9 @@
import java.net.URI;
import java.util.*;
+// Java 2 enterprise packages
+import javax.transaction.xa.XAResource;
+
// Third party packages
import org.apache.log4j.Logger;
import org.jrdf.graph.*;
@@ -113,6 +118,10 @@
/** Source of transactions. */
private final MulgaraTransactionManager transactionManager;
+ private MulgaraTransactionFactory transactionFactory;
+ private MulgaraInternalTransactionFactory internalFactory;
+ private MulgaraExternalTransactionFactory externalFactory;
+
/** The name of the rule loader to use */
private String ruleLoaderClassName;
@@ -226,6 +235,9 @@
this.temporaryModelTypeURI = temporaryModelTypeURI;
this.ruleLoaderClassName = ruleLoaderClassName;
+ this.transactionFactory = null;
+ this.internalFactory = null;
+
if (logger.isDebugEnabled()) {
logger.debug("Constructed DatabaseSession");
}
@@ -297,7 +309,7 @@
// Methods implementing Session
//
- public void insert(URI modelURI, Set<Triple> statements) throws QueryException {
+ public void insert(URI modelURI, Set<? extends Triple> statements) throws QueryException {
modify(modelURI, statements, ASSERT_STATEMENTS);
}
@@ -305,7 +317,7 @@
modify(modelURI, query, ASSERT_STATEMENTS);
}
- public void delete(URI modelURI, Set<Triple> statements) throws QueryException {
+ public void delete(URI modelURI, Set<? extends Triple> statements) throws QueryException {
modify(modelURI, statements, DENY_STATEMENTS);
}
@@ -510,8 +522,9 @@
if (logger.isInfoEnabled()) {
logger.info("setAutoCommit(" + autoCommit + ") called.");
}
+ assertInternallyManagedXA();
try {
- transactionManager.setAutoCommit(this, autoCommit);
+ internalFactory.setAutoCommit(this, autoCommit);
} catch (MulgaraTransactionException em) {
throw new QueryException("Error setting autocommit", em);
}
@@ -519,8 +532,9 @@
public void commit() throws QueryException {
logger.info("Committing transaction");
+ assertInternallyManagedXA();
try {
- transactionManager.commit(this);
+ internalFactory.commit(this);
} catch (MulgaraTransactionException em) {
throw new QueryException("Error performing commit", em);
}
@@ -528,8 +542,9 @@
public void rollback() throws QueryException {
logger.info("Rollback transaction");
+ assertInternallyManagedXA();
try {
- transactionManager.rollback(this);
+ internalFactory.rollback(this);
} catch (MulgaraTransactionException em) {
throw new QueryException("Error performing rollback", em);
}
@@ -538,9 +553,11 @@
public void close() throws QueryException {
logger.info("Closing session");
try {
- transactionManager.rollbackCurrentTransactions(this);
- } catch (MulgaraTransactionException em) {
- throw new QueryException("Error closing session. Forced close required", em);
+ transactionManager.closingSession(this);
+ transactionFactory = null;
+ } catch (MulgaraTransactionException em2) {
+ logger.error("Error force-closing session", em2);
+ throw new QueryException("Error force-closing session.", em2);
}
}
@@ -574,25 +591,23 @@
*/
private synchronized void backup(OutputStream outputStream, URI serverURI, URI destinationURI)
throws QueryException {
- execute(
- new BackupOperation(outputStream, serverURI, destinationURI),
+ execute(new BackupOperation(outputStream, serverURI, destinationURI),
"Unable to backup to " + destinationURI);
}
//
// Internal utility methods.
//
- protected void modify(URI modelURI, Set<Triple> statements, boolean insert) throws QueryException
+ protected void modify(URI modelURI, Set<? extends Triple> statements, boolean insert) throws QueryException
{
if (logger.isInfoEnabled()) {
- logger.info("Inserting statements into " + modelURI);
+ logger.info("Modifying (ins:" + insert + ") : " + modelURI);
}
if (logger.isDebugEnabled()) {
- logger.debug("Inserting statements: " + statements);
+ logger.debug("Modifying statements: " + statements);
}
- execute(new ModifyModelOperation(modelURI, statements, insert),
- "Could not commit insert");
+ execute(new ModifyModelOperation(modelURI, statements, insert), "Could not commit modify");
}
private void modify(URI modelURI, Query query, boolean insert) throws QueryException
@@ -602,7 +617,7 @@
}
execute(new ModifyModelOperation(modelURI, query, insert, this),
- "Unable to modify " + modelURI);
+ "Unable to modify " + modelURI);
}
/**
@@ -613,9 +628,10 @@
*/
private void execute(Operation operation, String errorString) throws QueryException
{
+ ensureTransactionFactorySelected();
try {
MulgaraTransaction transaction =
- transactionManager.getTransaction(this, operation.isWriteOperation());
+ transactionFactory.getTransaction(this, operation.isWriteOperation());
transaction.execute(operation, resolverSessionFactory, metadata);
} catch (MulgaraTransactionException em) {
logger.info("Error executing operation: " + errorString, em);
@@ -636,4 +652,36 @@
systemResolverFactory,
writing);
}
+
+ private void ensureTransactionFactorySelected() throws QueryException {
+ if (transactionFactory == null) {
+ assertInternallyManagedXA();
+ }
+ }
+
+ private void assertInternallyManagedXA() throws QueryException {
+ if (transactionFactory == null) {
+ transactionFactory = internalFactory = transactionManager.getInternalFactory();
+ } else if (internalFactory == null) {
+ throw new QueryException("Attempt to use internal transaction control in externally managed session");
+ }
+ }
+
+ private void assertExternallyManagedXA() throws QueryException {
+ if (transactionFactory == null) {
+ transactionFactory = externalFactory = transactionManager.getExternalFactory();
+ } else if (externalFactory == null) {
+ throw new QueryException("Attempt to use external transaction control in internally managed session");
+ }
+ }
+
+ public XAResource getXAResource() throws QueryException {
+ assertExternallyManagedXA();
+ return externalFactory.getXAResource(this, true);
+ }
+
+ public XAResource getReadOnlyXAResource() throws QueryException {
+ assertExternallyManagedXA();
+ return externalFactory.getXAResource(this, false);
+ }
}
Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionListQueryUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionListQueryUnitTest.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionListQueryUnitTest.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -282,7 +282,7 @@
private Query selectAll(URI modelURI) throws Exception {
//select $s $p $o
- ConstraintElement [] vars = new Variable[] {
+ Variable[] vars = new Variable[] {
StatementStore.VARIABLES[0],
StatementStore.VARIABLES[1],
StatementStore.VARIABLES[2]
@@ -295,7 +295,7 @@
ConstraintImpl varConstraint = new ConstraintImpl(vars[0], vars[1], vars[2]);
return new Query(
- Arrays.asList((Object[])vars), // variable list
+ Arrays.asList((SelectElement[])vars), // variable list
model, // model expression
varConstraint, // constraint expr
null, // no having
Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionUnitTest.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/DatabaseSessionUnitTest.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -558,7 +558,7 @@
// Evaluate the query
Answer answer = new ArrayAnswer(session.query(new Query(
- (List<Object>)(List)test.selectList, // SELECT
+ test.selectList, // SELECT
test.model, // FROM
test.query, // WHERE
null, // HAVING
Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/ExternalTransactionUnitTest.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,2025 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com) under contract to
+ * Topaz Foundation. Portions created under this contract are
+ * Copyright (c) 2007 Topaz Foundation
+ * All Rights Reserved.
+ *
+ * scaffolding based on AdvDatabaseSessionUnitTest.java
+ */
+
+package org.mulgara.resolver;
+
+// Java 2 standard packages
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import junit.framework.*; // JUnit
+import org.apache.log4j.Logger; // Log4J
+import org.jrdf.graph.SubjectNode;
+import org.jrdf.graph.PredicateNode;
+import org.jrdf.graph.ObjectNode;
+
+// Locally written packages
+import org.mulgara.query.*;
+import org.mulgara.query.rdf.Mulgara;
+import org.mulgara.query.rdf.URIReferenceImpl;
+import org.mulgara.query.rdf.TripleImpl;
+import org.mulgara.server.Session;
+import org.mulgara.util.FileUtil;
+
+/**
+ * Testing Externally Mediated Transactions.
+ *
+ * @created 2007-11-27
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ * @company <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
+ * Software Pty Ltd</a>
+ * @copyright ©2006 <a href="http://www.topazproject.org/">Topaz Project
+ * Foundation</a>
+ *
+ * @licence Open Software License v3.0</a>
+ */
+
+public class ExternalTransactionUnitTest extends TestCase
+{
+ /** Logger. */
+ private static Logger logger =
+ Logger.getLogger(ExternalTransactionUnitTest.class.getName());
+
+ private static final URI databaseURI;
+
+ private static final URI systemModelURI;
+
+ private static final URI modelURI;
+ private static final URI model2URI;
+ private static final URI model3URI;
+ private static final URI model4URI;
+ private static final URI model5URI;
+
+ static {
+ try {
+ databaseURI = new URI("local://database");
+ systemModelURI = new URI("local://database#");
+ modelURI = new URI("local://database#model");
+ model2URI = new URI("local://database#model2");
+ model3URI = new URI("local://database#model3");
+ model4URI = new URI("local://database#model4");
+ model5URI = new URI("local://database#model5");
+ } catch (URISyntaxException e) {
+ throw new Error("Bad hardcoded URI", e);
+ }
+ }
+
+ private static Database database = null;
+
+ public ExternalTransactionUnitTest(String name)
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ TestSuite suite = new TestSuite();
+ suite.addTest(new ExternalTransactionUnitTest("testSimpleOnePhaseCommit"));
+ suite.addTest(new ExternalTransactionUnitTest("testSimpleTwoPhaseCommit"));
+ suite.addTest(new ExternalTransactionUnitTest("testBasicQuery"));
+ suite.addTest(new ExternalTransactionUnitTest("testMultipleQuery"));
+ suite.addTest(new ExternalTransactionUnitTest("testBasicReadOnlyQuery"));
+ suite.addTest(new ExternalTransactionUnitTest("testConcurrentQuery"));
+ suite.addTest(new ExternalTransactionUnitTest("testConcurrentReadWrite"));
+ suite.addTest(new ExternalTransactionUnitTest("testSubqueryQuery"));
+ suite.addTest(new ExternalTransactionUnitTest("testConcurrentSubqueryQuery"));
+ suite.addTest(new ExternalTransactionUnitTest("testExplicitIsolationQuerySingleSession"));
+ suite.addTest(new ExternalTransactionUnitTest("testConcurrentExplicitTxn"));
+ suite.addTest(new ExternalTransactionUnitTest("testExplicitRollbackIsolationQuery"));
+ suite.addTest(new ExternalTransactionUnitTest("testExternalInternalIsolation"));
+ suite.addTest(new ExternalTransactionUnitTest("testInternalExternalIsolation"));
+ suite.addTest(new ExternalTransactionUnitTest("testInternalExternalConcurrentTxn"));
+ suite.addTest(new ExternalTransactionUnitTest("testExternalInternalConcurrentTxn"));
+ suite.addTest(new ExternalTransactionUnitTest("testInternalExternalConcurrentTxnRollback"));
+ suite.addTest(new ExternalTransactionUnitTest("testExternalInternalConcurrentTxnRollback"));
+ suite.addTest(new ExternalTransactionUnitTest("testInternalSerialMultipleSessions"));
+
+ return suite;
+ }
+
+ /**
+ * Create test objects.
+ */
+ public void setUp() throws Exception {
+ if (database == null) {
+ // Create the persistence directory
+ File persistenceDirectory =
+ new File(new File(System.getProperty("cvs.root")), "testDatabase");
+ if (persistenceDirectory.isDirectory()) {
+ if (!FileUtil.deleteDirectory(persistenceDirectory)) {
+ throw new RuntimeException(
+ "Unable to remove old directory " + persistenceDirectory
+ );
+ }
+ }
+ if (!persistenceDirectory.mkdirs()) {
+ throw new Exception("Unable to create directory "+persistenceDirectory);
+ }
+
+ // Define the the node pool factory
+ String nodePoolFactoryClassName =
+ "org.mulgara.store.nodepool.xa.XANodePoolFactory";
+
+ // Define the string pool factory
+ String stringPoolFactoryClassName =
+ "org.mulgara.store.stringpool.xa.XAStringPoolFactory";
+
+ String tempNodePoolFactoryClassName =
+ "org.mulgara.store.nodepool.memory.MemoryNodePoolFactory";
+
+ // Define the string pool factory
+ String tempStringPoolFactoryClassName =
+ "org.mulgara.store.stringpool.memory.MemoryStringPoolFactory";
+
+ // Define the resolver factory used to manage system models
+ String systemResolverFactoryClassName =
+ "org.mulgara.resolver.store.StatementStoreResolverFactory";
+
+ // Define the resolver factory used to manage system models
+ String tempResolverFactoryClassName =
+ "org.mulgara.resolver.memory.MemoryResolverFactory";
+
+ String ruleLoaderFactoryClassName =
+ "org.mulgara.rules.RuleLoaderFactory";
+
+ // Create a database which keeps its system models on the Java heap
+ database = new Database(
+ databaseURI,
+ persistenceDirectory,
+ null, // no security domain
+ new JotmTransactionManagerFactory(),
+ 0, // default transaction timeout
+ nodePoolFactoryClassName, // persistent
+ new File(persistenceDirectory, "xaNodePool"),
+ stringPoolFactoryClassName, // persistent
+ new File(persistenceDirectory, "xaStringPool"),
+ systemResolverFactoryClassName, // persistent
+ new File(persistenceDirectory, "xaStatementStore"),
+ tempNodePoolFactoryClassName, // temporary nodes
+ null, // no dir for temp nodes
+ tempStringPoolFactoryClassName, // temporary strings
+ null, // no dir for temp strings
+ tempResolverFactoryClassName, // temporary models
+ null, // no dir for temp models
+ "", // no rule loader
+ "org.mulgara.content.rdfxml.RDFXMLContentHandler");
+
+ database.addResolverFactory("org.mulgara.resolver.url.URLResolverFactory", null);
+ }
+ }
+
+
+ /**
+ * The teardown method for JUnit
+ */
+ public void tearDown()
+ {
+ }
+
+ //
+ // Test cases
+ //
+
+ private static class TestXid implements Xid {
+ private int xid;
+ public TestXid(int xid) {
+ this.xid = xid;
+ }
+
+ public int getFormatId() {
+ return 'X';
+ }
+
+ public byte[] getBranchQualifier() {
+ return new byte[] {
+ (byte)(xid >> 0x00),
+ (byte)(xid >> 0x08)
+ };
+ }
+
+ public byte[] getGlobalTransactionId() {
+ return new byte[] {
+ (byte)(xid >> 0x10),
+ (byte)(xid >> 0x18)
+ };
+ }
+ }
+
+ /**
+ * Test the {@link DatabaseSession#create} method.
+ * As a side-effect, creates the model required by the next tests.
+ */
+ public void testSimpleOnePhaseCommit() throws URISyntaxException
+ {
+ logger.info("testSimpleOnePhaseCommit");
+
+ try {
+ DatabaseSession session = (DatabaseSession)database.newSession();
+ XAResource resource = session.getXAResource();
+ Xid xid = new TestXid(1);
+ resource.start(xid, XAResource.TMNOFLAGS);
+ try {
+ session.createModel(modelURI, null);
+ resource.end(xid, XAResource.TMSUCCESS);
+ resource.commit(xid, true);
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+
+ /**
+ * Test two phase commit.
+ * As a side-effect, loads the model required by the next tests.
+ */
+ public void testSimpleTwoPhaseCommit() throws URISyntaxException
+ {
+ logger.info("testSimpleTwoPhaseCommit");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ DatabaseSession session = (DatabaseSession)database.newSession();
+ XAResource resource = session.getXAResource();
+ Xid xid = new TestXid(1);
+ resource.start(xid, XAResource.TMNOFLAGS);
+ try {
+ session.setModel(modelURI, new ModelResource(fileURI));
+ resource.end(xid, XAResource.TMSUCCESS);
+ resource.prepare(xid);
+ resource.commit(xid, false);
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+
+ public void testBasicQuery() throws URISyntaxException {
+ logger.info("Testing basicQuery");
+
+ try {
+ // Load some test data
+ DatabaseSession session = (DatabaseSession)database.newSession();
+ try {
+ XAResource resource = session.getXAResource();
+ Xid xid = new TestXid(1);
+ resource.start(xid, XAResource.TMNOFLAGS);
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer);
+ answer.close();
+
+ resource.end(xid, XAResource.TMSUCCESS);
+ resource.commit(xid, true);
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testMultipleQuery() throws URISyntaxException {
+ logger.info("Testing MultipleQuery");
+
+ try {
+ // Load some test data
+ Session session = database.newSession();
+ XAResource resource = session.getXAResource();
+ Xid xid = new TestXid(1);
+ resource.start(xid, XAResource.TMNOFLAGS);
+ try {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer1 = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ Answer answer2 = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ compareResults(answer1, answer2);
+
+ answer1.close();
+ answer2.close();
+
+ resource.end(xid, XAResource.TMSUCCESS);
+ resource.commit(xid, true);
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testBasicReadOnlyQuery() throws URISyntaxException {
+ logger.info("Testing basicReadOnlyQuery");
+
+ try {
+ // Load some test data
+ DatabaseSession session = (DatabaseSession)database.newSession();
+ try {
+ XAResource resource = session.getReadOnlyXAResource();
+ Xid xid = new TestXid(1);
+ resource.start(xid, XAResource.TMNOFLAGS);
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer);
+ answer.close();
+
+ resource.end(xid, XAResource.TMSUCCESS);
+ resource.commit(xid, true);
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testConcurrentQuery() throws URISyntaxException {
+ logger.info("Testing concurrentQuery");
+
+ try {
+ // Load some test data
+ Session session = database.newSession();
+ XAResource resource = session.getReadOnlyXAResource();
+ Xid xid1 = new TestXid(1);
+ Xid xid2 = new TestXid(2);
+ resource.start(xid1, XAResource.TMNOFLAGS);
+ try {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer1 = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+ resource.end(xid1, XAResource.TMSUSPEND);
+ resource.start(xid2, XAResource.TMNOFLAGS);
+
+ Answer answer2 = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+ resource.end(xid2, XAResource.TMSUSPEND);
+
+ compareResults(answer1, answer2);
+
+ answer1.close();
+ answer2.close();
+
+ resource.start(xid1, XAResource.TMRESUME);
+ resource.end(xid1, XAResource.TMSUCCESS);
+ resource.end(xid2, XAResource.TMSUCCESS);
+ resource.commit(xid1, true);
+ resource.commit(xid2, true);
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ /**
+ * Note: What this test does is a really bad idea - there is no
+ * isolation provided as each operation is within its own
+ * transaction. It does however provide a good test.
+ */
+ public void testConcurrentReadWrite() throws URISyntaxException {
+ logger.info("Testing concurrentReadWrite");
+
+ try {
+ Session session = database.newSession();
+ XAResource roResource = session.getReadOnlyXAResource();
+ XAResource rwResource = session.getXAResource();
+ Xid xid1 = new TestXid(1);
+
+ rwResource.start(xid1, XAResource.TMNOFLAGS);
+ session.createModel(model2URI, null);
+ rwResource.end(xid1, XAResource.TMSUSPEND);
+
+ try {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ Xid xid2 = new TestXid(2);
+ roResource.start(xid2, XAResource.TMNOFLAGS);
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ roResource.end(xid2, XAResource.TMSUSPEND);
+ answer.beforeFirst();
+ while (answer.next()) {
+ rwResource.start(xid1, XAResource.TMRESUME);
+ session.insert(model2URI, Collections.singleton(new TripleImpl(
+ (SubjectNode)answer.getObject(0),
+ (PredicateNode)answer.getObject(1),
+ (ObjectNode)answer.getObject(2))));
+ rwResource.end(xid1, XAResource.TMSUSPEND);
+ }
+ answer.close();
+
+ rwResource.end(xid1, XAResource.TMSUCCESS);
+ rwResource.commit(xid1, true);
+
+ Xid xid3 = new TestXid(3);
+ roResource.start(xid3, XAResource.TMNOFLAGS);
+
+ Answer answer2 = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model2URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ roResource.end(xid3, XAResource.TMSUSPEND);
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer2);
+ answer2.close();
+
+ Xid xid4 = new TestXid(4);
+ rwResource.start(xid4, XAResource.TMNOFLAGS);
+ session.removeModel(model2URI);
+ rwResource.end(xid4, XAResource.TMSUCCESS);
+ rwResource.commit(xid4, true);
+
+ roResource.end(xid2, XAResource.TMSUCCESS);
+ roResource.commit(xid2, true);
+ roResource.end(xid3, XAResource.TMSUCCESS);
+ roResource.commit(xid3, true);
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testSubqueryQuery() throws URISyntaxException {
+ logger.info("Testing subqueryQuery");
+
+ try {
+ // Load some test data
+ Session session = database.newSession();
+ XAResource roResource = session.getReadOnlyXAResource();
+ Xid xid1 = new TestXid(1);
+ roResource.start(xid1, XAResource.TMNOFLAGS);
+
+ try {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(new Subquery(new Variable("k0"), new Query(
+ Collections.singletonList(objectVariable),
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(objectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ )));
+
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ new URIReferenceImpl(new URI("test:p03")),
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ roResource.end(xid1, XAResource.TMSUSPEND);
+
+ answer.beforeFirst();
+
+ assertTrue(answer.next());
+ assertEquals(new URIReferenceImpl(new URI("test:s01")),
+ answer.getObject(0));
+ Answer sub1 = (Answer)answer.getObject(1);
+ compareResults(new String[][] { new String[] { "test:o01" },
+ new String[] { "test:o02" } }, sub1);
+ sub1.close();
+
+ assertTrue(answer.next());
+ assertEquals(new URIReferenceImpl(new URI("test:s02")),
+ answer.getObject(0));
+ Answer sub2 = (Answer)answer.getObject(1);
+ compareResults(new String[][] { new String[] { "test:o02" },
+ new String[] { "test:o03" } }, sub2);
+ // Leave sub2 open.
+
+ assertFalse(answer.next());
+ answer.close();
+ sub2.close();
+
+ // Leave transaction to be closed on session close.
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testConcurrentSubqueryQuery() throws URISyntaxException {
+ logger.info("Testing concurrentSubqueryQuery");
+
+ try {
+ Session session = database.newSession();
+ XAResource rwResource = session.getXAResource();
+ Xid xid1 = new TestXid(1);
+ rwResource.start(xid1, XAResource.TMNOFLAGS);
+
+ try {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(new Subquery(new Variable("k0"), new Query(
+ Collections.singletonList(objectVariable),
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(objectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ )));
+
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ new URIReferenceImpl(new URI("test:p03")),
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ answer.beforeFirst();
+
+ assertTrue(answer.next());
+ assertEquals(new URIReferenceImpl(new URI("test:s01")),
+ answer.getObject(0));
+ Answer sub1 = (Answer)answer.getObject(1);
+ assertTrue(answer.next());
+ assertEquals(new URIReferenceImpl(new URI("test:s02")),
+ answer.getObject(0));
+ Answer sub2 = (Answer)answer.getObject(1);
+ assertFalse(answer.next());
+
+ assertEquals(1, sub1.getNumberOfVariables());
+ assertEquals(1, sub2.getNumberOfVariables());
+ sub1.beforeFirst();
+ sub2.beforeFirst();
+ assertTrue(sub1.next());
+ assertTrue(sub2.next());
+ assertEquals(new URIReferenceImpl(new URI("test:o01")), sub1.getObject(0));
+ assertEquals(new URIReferenceImpl(new URI("test:o02")), sub2.getObject(0));
+
+ rwResource.end(xid1, XAResource.TMSUSPEND);
+
+ assertTrue(sub1.next());
+ assertTrue(sub2.next());
+ assertEquals(new URIReferenceImpl(new URI("test:o02")), sub1.getObject(0));
+ assertEquals(new URIReferenceImpl(new URI("test:o03")), sub2.getObject(0));
+ assertFalse(sub1.next());
+ assertFalse(sub2.next());
+
+ answer.close();
+
+ rwResource.end(xid1, XAResource.TMSUCCESS);
+ rwResource.commit(xid1, true);
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testExplicitIsolationQuerySingleSession() throws URISyntaxException
+ {
+ logger.info("testExplicitIsolationQuery");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session = database.newSession();
+ try {
+ XAResource roResource = session.getReadOnlyXAResource();
+ XAResource rwResource = session.getXAResource();
+ Xid xid1 = new TestXid(1); // Initial create model.
+ Xid xid2 = new TestXid(2); // Started before setModel.
+ Xid xid3 = new TestXid(3); // setModel.
+ Xid xid4 = new TestXid(4); // Started before setModel prepares
+ Xid xid5 = new TestXid(5); // Started before setModel commits
+ Xid xid6 = new TestXid(6); // Started after setModel commits
+ Xid xid7 = new TestXid(7); // Final remove model.
+
+ rwResource.start(xid1, XAResource.TMNOFLAGS);
+ session.createModel(model3URI, null);
+ rwResource.end(xid1, XAResource.TMSUCCESS);
+ rwResource.commit(xid1, true);
+
+ // Nothing visible.
+ roResource.start(xid2, XAResource.TMNOFLAGS);
+ assertChangeNotVisible(session);
+ roResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Perform update
+ rwResource.start(xid3, XAResource.TMNOFLAGS);
+ session.setModel(model3URI, new ModelResource(fileURI));
+ rwResource.end(xid3, XAResource.TMSUSPEND);
+
+ // Check uncommitted change not visible
+ roResource.start(xid4, XAResource.TMNOFLAGS);
+ assertChangeNotVisible(session);
+ roResource.end(xid4, XAResource.TMSUSPEND);
+
+ // Check original phase unaffected.
+ roResource.start(xid2, XAResource.TMRESUME);
+ assertChangeNotVisible(session);
+ roResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Check micro-commit visible to current-phase
+ rwResource.start(xid3, XAResource.TMRESUME);
+ assertChangeVisible(session);
+ // Perform prepare
+ rwResource.end(xid3, XAResource.TMSUCCESS);
+ rwResource.prepare(xid3);
+
+ // Check original phase unaffected
+ roResource.start(xid2, XAResource.TMRESUME);
+ assertChangeNotVisible(session);
+ roResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Check pre-prepare phase unaffected
+ roResource.start(xid4, XAResource.TMRESUME);
+ assertChangeNotVisible(session);
+ roResource.end(xid4, XAResource.TMSUSPEND);
+
+ // Check committed phase unaffected.
+ roResource.start(xid5, XAResource.TMNOFLAGS);
+ assertChangeNotVisible(session);
+ roResource.end(xid5, XAResource.TMSUSPEND);
+
+ // Do commit
+ rwResource.commit(xid3, false);
+
+ // Check original phase
+ roResource.start(xid2, XAResource.TMRESUME);
+ assertChangeNotVisible(session);
+ roResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Check pre-prepare
+ roResource.start(xid4, XAResource.TMRESUME);
+ assertChangeNotVisible(session);
+ roResource.end(xid4, XAResource.TMSUSPEND);
+
+ // Check pre-commit
+ roResource.start(xid5, XAResource.TMRESUME);
+ assertChangeNotVisible(session);
+ roResource.end(xid5, XAResource.TMSUSPEND);
+
+ // Check committed phase is now updated
+ roResource.start(xid6, XAResource.TMNOFLAGS);
+ assertChangeVisible(session);
+
+ // Cleanup transactions.
+ roResource.end(xid6, XAResource.TMSUCCESS);
+ roResource.end(xid2, XAResource.TMSUCCESS);
+ roResource.end(xid4, XAResource.TMSUCCESS);
+ roResource.end(xid5, XAResource.TMSUCCESS);
+ roResource.commit(xid2, true);
+ roResource.commit(xid4, true);
+ roResource.commit(xid5, true);
+ roResource.commit(xid6, true);
+
+ // Cleanup database
+ rwResource.start(xid7, XAResource.TMNOFLAGS);
+ session.removeModel(model3URI);
+ rwResource.end(xid7, XAResource.TMSUCCESS);
+ rwResource.commit(xid7, true);
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testExternalInternalIsolation() throws URISyntaxException
+ {
+ logger.info("testExplicitIsolationQuery");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ Session session2 = database.newSession();
+ try {
+ XAResource roResource = session1.getReadOnlyXAResource();
+ XAResource rwResource = session1.getXAResource();
+ Xid xid1 = new TestXid(1); // Initial create model.
+ Xid xid2 = new TestXid(2); // Main Test.
+ Xid xid3 = new TestXid(3); // Cleanup test.
+
+ rwResource.start(xid1, XAResource.TMNOFLAGS);
+ session1.createModel(model3URI, null);
+ rwResource.end(xid1, XAResource.TMSUCCESS);
+ rwResource.commit(xid1, true);
+
+ // Nothing visible.
+ assertChangeNotVisible(session2);
+
+ // Perform update
+ rwResource.start(xid2, XAResource.TMNOFLAGS);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+ rwResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Check uncommitted change not visible
+ assertChangeNotVisible(session2);
+
+ // Check micro-commit visible to current-phase
+ rwResource.start(xid2, XAResource.TMRESUME);
+ assertChangeVisible(session1);
+ // Perform prepare
+ rwResource.end(xid2, XAResource.TMSUCCESS);
+ rwResource.prepare(xid2);
+
+ // Check original phase unaffected
+ assertChangeNotVisible(session2);
+
+ // Do commit
+ rwResource.commit(xid2, false);
+
+ // Check committed phase is now updated
+ assertChangeVisible(session2);
+
+ // Cleanup database
+ session2.removeModel(model3URI);
+ } finally {
+ session2.close();
+ }
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testInternalExternalIsolation() throws URISyntaxException
+ {
+ logger.info("testExplicitIsolationQuery");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ Session session2 = database.newSession();
+ try {
+ XAResource roResource = session2.getReadOnlyXAResource();
+ XAResource rwResource = session2.getXAResource();
+ Xid xid1 = new TestXid(1); // Pre-update
+ Xid xid2 = new TestXid(2); // Post-update/Pre-commit
+ Xid xid3 = new TestXid(3); // Post-commit
+
+ session1.createModel(model3URI, null);
+
+ // Nothing visible.
+ roResource.start(xid1, XAResource.TMNOFLAGS);
+ assertChangeNotVisible(session2);
+ roResource.end(xid1, XAResource.TMSUSPEND);
+
+ // Perform update with autocommit off
+ session1.setAutoCommit(false);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+
+ // Check uncommitted change not visible
+ roResource.start(xid2, XAResource.TMNOFLAGS);
+ assertChangeNotVisible(session2);
+ roResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Check original phase unaffected.
+ roResource.start(xid1, XAResource.TMRESUME);
+ assertChangeNotVisible(session2);
+ roResource.end(xid1, XAResource.TMSUSPEND);
+
+ // Check micro-commit visible to current-phase
+ assertChangeVisible(session1);
+ session1.setAutoCommit(true);
+
+ // Check original phase unaffected
+ roResource.start(xid1, XAResource.TMRESUME);
+ assertChangeNotVisible(session2);
+ roResource.end(xid1, XAResource.TMSUSPEND);
+
+ // Check pre-commit phase unaffected
+ roResource.start(xid2, XAResource.TMRESUME);
+ assertChangeNotVisible(session2);
+ roResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Check committed phase is now updated and write-lock available
+ rwResource.start(xid3, XAResource.TMNOFLAGS);
+ assertChangeVisible(session2);
+
+ // Check internal transaction read-only
+ assertChangeVisible(session1);
+
+ // Cleanup transactions.
+ rwResource.end(xid3, XAResource.TMSUCCESS);
+ roResource.end(xid2, XAResource.TMSUCCESS);
+ roResource.end(xid1, XAResource.TMSUCCESS);
+ roResource.commit(xid1, true);
+ roResource.commit(xid2, true);
+ rwResource.commit(xid3, true);
+
+ // Cleanup database (check write-lock available again)
+ session1.removeModel(model3URI);
+ } finally {
+ session2.close();
+ }
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ private void assertChangeVisible(Session session) throws Exception {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer);
+ answer.close();
+ }
+
+ private void assertChangeNotVisible(Session session) throws Exception {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+ answer.beforeFirst();
+ assertFalse(answer.next());
+ answer.close();
+ }
+
+ /**
+ * Test two simultaneous, explicit transactions, in two threads. The second one should block
+ * until the first one sets auto-commit back to true.
+ */
+ public void testConcurrentExplicitTxn() throws URISyntaxException
+ {
+ logger.info("testConcurrentExplicitTxn");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ XAResource resource1 = session1.getXAResource();
+ resource1.start(new TestXid(1), XAResource.TMNOFLAGS);
+ session1.createModel(model3URI, null);
+ resource1.end(new TestXid(1), XAResource.TMSUCCESS);
+ resource1.commit(new TestXid(1), true);
+
+ resource1.start(new TestXid(2), XAResource.TMNOFLAGS);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+
+ final boolean[] tx2Started = new boolean[] { false };
+
+ Thread t2 = new Thread("tx2Test") {
+ public void run() {
+ try {
+ Session session2 = database.newSession();
+ XAResource resource2 = session2.getXAResource();
+ try {
+ resource2.start(new TestXid(3), XAResource.TMNOFLAGS);
+
+ synchronized (tx2Started) {
+ tx2Started[0] = true;
+ tx2Started.notify();
+ }
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session2.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer);
+ answer.close();
+
+ resource2.end(new TestXid(3), XAResource.TMSUCCESS);
+ resource2.commit(new TestXid(3), true);
+ } finally {
+ session2.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+ };
+ t2.start();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ resource1.commit(new TestXid(2), true);
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ assertTrue("second transaction should've started", tx2Started[0]);
+ }
+ }
+
+ try {
+ t2.join(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-terminated interrupted", ie);
+ fail(ie);
+ }
+ assertFalse("second transaction should've terminated", t2.isAlive());
+
+ resource1.start(new TestXid(4), XAResource.TMNOFLAGS);
+ session1.removeModel(model3URI);
+ resource1.end(new TestXid(4), XAResource.TMSUCCESS);
+ resource1.commit(new TestXid(4), true);
+
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ /**
+ * Test two simultaneous transactions, in two threads. The second one should block
+ * until the first one sets auto-commit back to true.
+ */
+ public void testExternalInternalConcurrentTxn() throws URISyntaxException
+ {
+ logger.info("testConcurrentExplicitTxn");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ XAResource resource1 = session1.getXAResource();
+ resource1.start(new TestXid(1), XAResource.TMNOFLAGS);
+ session1.createModel(model3URI, null);
+ resource1.end(new TestXid(1), XAResource.TMSUCCESS);
+ resource1.commit(new TestXid(1), true);
+
+ resource1.start(new TestXid(2), XAResource.TMNOFLAGS);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+
+ final boolean[] tx2Started = new boolean[] { false };
+
+ Thread t2 = new Thread("tx2Test") {
+ public void run() {
+ try {
+ Session session2 = database.newSession();
+ try {
+ session2.setAutoCommit(false);
+
+ synchronized (tx2Started) {
+ tx2Started[0] = true;
+ tx2Started.notify();
+ }
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session2.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer);
+ answer.close();
+
+ session2.setAutoCommit(true);
+ } finally {
+ session2.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+ };
+ t2.start();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ resource1.commit(new TestXid(2), true);
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ assertTrue("second transaction should've started", tx2Started[0]);
+ }
+ }
+
+ try {
+ t2.join(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-terminated interrupted", ie);
+ fail(ie);
+ }
+ assertFalse("second transaction should've terminated", t2.isAlive());
+
+ resource1.start(new TestXid(4), XAResource.TMNOFLAGS);
+ session1.removeModel(model3URI);
+ resource1.end(new TestXid(4), XAResource.TMSUCCESS);
+ resource1.commit(new TestXid(4), true);
+
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+
+ /**
+ * Test two simultaneous transactions, in two threads. The second one should block
+ * until the first one sets auto-commit back to true.
+ */
+ public void testInternalExternalConcurrentTxn() throws URISyntaxException
+ {
+ logger.info("testConcurrentExplicitTxn");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ session1.createModel(model3URI, null);
+
+ session1.setAutoCommit(false);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+
+ final boolean[] tx2Started = new boolean[] { false };
+
+ Thread t2 = new Thread("tx2Test") {
+ public void run() {
+ try {
+ Session session2 = database.newSession();
+ try {
+ XAResource resource = session2.getXAResource();
+ resource.start(new TestXid(1), XAResource.TMNOFLAGS);
+
+ synchronized (tx2Started) {
+ tx2Started[0] = true;
+ tx2Started.notify();
+ }
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session2.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer);
+ answer.close();
+
+ resource.end(new TestXid(1), XAResource.TMSUCCESS);
+ resource.rollback(new TestXid(1));
+ } finally {
+ session2.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+ };
+ t2.start();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ session1.commit();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ session1.setAutoCommit(true);
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ assertTrue("second transaction should've started", tx2Started[0]);
+ }
+ }
+
+ try {
+ t2.join(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-terminated interrupted", ie);
+ fail(ie);
+ }
+ assertFalse("second transaction should've terminated", t2.isAlive());
+
+ session1.removeModel(model3URI);
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ /**
+ * Test two simultaneous transactions, in two threads. The second one should block
+ * until the first one sets auto-commit back to true.
+ */
+ public void testExternalInternalConcurrentTxnRollback() throws URISyntaxException
+ {
+ logger.info("testConcurrentExplicitTxn");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ XAResource resource1 = session1.getXAResource();
+ resource1.start(new TestXid(1), XAResource.TMNOFLAGS);
+ session1.createModel(model3URI, null);
+ resource1.end(new TestXid(1), XAResource.TMSUCCESS);
+ resource1.commit(new TestXid(1), true);
+
+ resource1.start(new TestXid(2), XAResource.TMNOFLAGS);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+
+ final boolean[] tx2Started = new boolean[] { false };
+
+ Thread t2 = new Thread("tx2Test") {
+ public void run() {
+ try {
+ Session session2 = database.newSession();
+ try {
+ session2.setAutoCommit(false);
+
+ synchronized (tx2Started) {
+ tx2Started[0] = true;
+ tx2Started.notify();
+ }
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session2.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ answer.beforeFirst();
+ assertFalse(answer.next());
+ answer.close();
+
+ session2.setAutoCommit(true);
+ } finally {
+ session2.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+ };
+ t2.start();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ resource1.rollback(new TestXid(2));
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ assertTrue("second transaction should've started", tx2Started[0]);
+ }
+ }
+
+ try {
+ t2.join(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-terminated interrupted", ie);
+ fail(ie);
+ }
+ assertFalse("second transaction should've terminated", t2.isAlive());
+
+ resource1.start(new TestXid(4), XAResource.TMNOFLAGS);
+ session1.removeModel(model3URI);
+ resource1.end(new TestXid(4), XAResource.TMSUCCESS);
+ resource1.commit(new TestXid(4), true);
+
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+
+ /**
+ * Test two simultaneous transactions, in two threads. The second one should block
+ * until the first one sets auto-commit back to true.
+ */
+ public void testInternalExternalConcurrentTxnRollback() throws URISyntaxException
+ {
+ logger.info("testConcurrentExplicitTxn");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ session1.createModel(model3URI, null);
+
+ session1.setAutoCommit(false);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+
+ final boolean[] tx2Started = new boolean[] { false };
+
+ Thread t2 = new Thread("tx2Test") {
+ public void run() {
+ try {
+ Session session2 = database.newSession();
+ try {
+ XAResource resource = session2.getXAResource();
+ resource.start(new TestXid(1), XAResource.TMNOFLAGS);
+
+ synchronized (tx2Started) {
+ tx2Started[0] = true;
+ tx2Started.notify();
+ }
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session2.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ answer.beforeFirst();
+ assertFalse(answer.next());
+ answer.close();
+
+ resource.end(new TestXid(1), XAResource.TMFAIL);
+ resource.rollback(new TestXid(1));
+ } finally {
+ session2.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+ };
+ t2.start();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ session1.rollback();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ session1.setAutoCommit(true);
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ assertTrue("second transaction should've started", tx2Started[0]);
+ }
+ }
+
+ try {
+ t2.join(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-terminated interrupted", ie);
+ fail(ie);
+ }
+ assertFalse("second transaction should've terminated", t2.isAlive());
+
+ session1.removeModel(model3URI);
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+
+ public void testExplicitRollbackIsolationQuery() throws URISyntaxException {
+ logger.info("testExplicitRollbackIsolationQuery");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session = database.newSession();
+ XAResource roResource = session.getReadOnlyXAResource();
+ XAResource rwResource = session.getXAResource();
+ try {
+ rwResource.start(new TestXid(1), XAResource.TMNOFLAGS);
+ session.createModel(model3URI, null);
+ rwResource.end(new TestXid(1), XAResource.TMSUCCESS);
+ rwResource.commit(new TestXid(1), true);
+
+ rwResource.start(new TestXid(2), XAResource.TMNOFLAGS);
+ session.setModel(model3URI, new ModelResource(fileURI));
+ rwResource.end(new TestXid(2), XAResource.TMSUSPEND);
+
+ roResource.start(new TestXid(3), XAResource.TMNOFLAGS);
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+ answer.beforeFirst();
+ assertFalse(answer.next());
+ answer.close();
+
+ roResource.end(new TestXid(3), XAResource.TMSUCCESS);
+ roResource.commit(new TestXid(3), true);
+
+ rwResource.end(new TestXid(2), XAResource.TMFAIL);
+ rwResource.rollback(new TestXid(2));
+
+ roResource.start(new TestXid(4), XAResource.TMNOFLAGS);
+ selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ answer.beforeFirst();
+ assertFalse(answer.next());
+ answer.close();
+
+ roResource.end(new TestXid(4), XAResource.TMFAIL);
+ roResource.rollback(new TestXid(4));
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+
+ /**
+ * Tests cleaning up a transaction on close. This test added in the process
+ * of fixing a bug reported by Ronald on the JTA-beta.
+ */
+ public void testInternalSerialMultipleSessions() throws URISyntaxException
+ {
+ logger.info("testInternalSerialMultipleSessions");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ Session session2 = database.newSession();
+ try {
+ session1.createModel(model4URI, null);
+
+ session1.setAutoCommit(false);
+ session1.setModel(model4URI, new ModelResource(fileURI));
+
+ session1.commit();
+ session1.close();
+
+ session2.setAutoCommit(false);
+ } finally {
+ session2.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ //
+ // Internal methods
+ //
+
+ private void compareResults(String[][] expected, Answer answer) throws Exception {
+ try {
+ answer.beforeFirst();
+ for (int i = 0; i < expected.length; i++) {
+ assertTrue("Answer short at row " + i, answer.next());
+ assertEquals(expected[i].length, answer.getNumberOfVariables());
+ for (int j = 0; j < expected[i].length; j++) {
+ URIReferenceImpl uri = new URIReferenceImpl(new URI(expected[i][j]));
+ assertEquals(uri, answer.getObject(j));
+ }
+ }
+ assertFalse(answer.next());
+ } catch (Exception e) {
+ logger.error("Failed test - " + answer);
+ answer.close();
+ throw e;
+ }
+ }
+
+ private void compareResults(Answer answer1, Answer answer2) throws Exception {
+ answer1.beforeFirst();
+ answer2.beforeFirst();
+ assertEquals(answer1.getNumberOfVariables(), answer2.getNumberOfVariables());
+ while (answer1.next()) {
+ assertTrue(answer2.next());
+ for (int i = 0; i < answer1.getNumberOfVariables(); i++) {
+ assertEquals(answer1.getObject(i), answer2.getObject(i));
+ }
+ }
+ assertFalse(answer2.next());
+ }
+
+
+ /**
+ * Fail with an unexpected exception
+ */
+ private void fail(Throwable throwable)
+ {
+ StringWriter stringWriter = new StringWriter();
+ throwable.printStackTrace(new PrintWriter(stringWriter));
+ fail(stringWriter.toString());
+ }
+}
Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/InternalResolver.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -126,6 +126,14 @@
Resolver systemResolver)
throws ResolverFactoryException
{
+ if (resolver == null) {
+ throw new IllegalArgumentException("Resolver 'null'");
+ } else if (resolverSession == null) {
+ throw new IllegalArgumentException("ResolverSession 'null'");
+ } else if (systemResolver == null) {
+ throw new IllegalArgumentException("SystemResolver 'null'");
+ }
+
// Initialize fields
this.resolver = resolver;
this.rdfType = rdfType;
Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/JotmTransactionStandaloneTest.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,2114 @@
+/*
+ * Copyright 2008 The Topaz Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+package org.mulgara.resolver;
+
+// Java 2 standard packages
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.RollbackException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+
+// Third party packages
+import junit.framework.*; // JUnit
+import org.apache.log4j.Logger; // Log4J
+import org.jrdf.graph.ObjectNode;
+import org.jrdf.graph.PredicateNode;
+import org.jrdf.graph.SubjectNode;
+import org.objectweb.jotm.Jotm;
+import org.objectweb.transaction.jta.TMService;
+
+// Locally written packages
+import org.mulgara.query.*;
+import org.mulgara.query.rdf.Mulgara;
+import org.mulgara.query.rdf.URIReferenceImpl;
+import org.mulgara.query.rdf.TripleImpl;
+import org.mulgara.server.Session;
+import org.mulgara.server.SessionFactory;
+import org.mulgara.server.driver.SessionFactoryFinder;
+import org.mulgara.util.FileUtil;
+
+import org.mulgara.query.QueryException;
+import org.mulgara.server.JRDFSession;
+import org.mulgara.server.SessionFactory;
+import org.mulgara.server.driver.SessionFactoryFinder;
+
+/**
+ * Regression test to test JTA integration with external JOTM instance.
+ *
+ * @created 2008-01-11
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ * @copyright ©2008 <a href="http://www.topazproject.org/">The Topaz Foundation</a>
+ * @licence Apache License v2.0
+ */
+public class JotmTransactionStandaloneTest extends TestCase
+{
+ /** Logger. */
+ private static Logger logger =
+ Logger.getLogger(JotmTransactionStandaloneTest.class.getName());
+
+ private static final URI databaseURI;
+
+ private static final URI systemModelURI;
+
+ private static final URI modelURI;
+ private static final URI model2URI;
+ private static final URI model3URI;
+ private static final URI model4URI;
+ private static final URI model5URI;
+
+ static {
+ try {
+ databaseURI = new URI("rmi://localhost/server1");
+ systemModelURI = new URI("rmi://localhost/server1#");
+ modelURI = new URI("rmi://localhost/server1#jotmmodel");
+ model2URI = new URI("rmi://localhost/server1#jotmmodel2");
+ model3URI = new URI("rmi://localhost/server1#jotmmodel3");
+ model4URI = new URI("rmi://localhost/server1#jotmmodel4");
+ model5URI = new URI("rmi://localhost/server1#jotmmodel5");
+ } catch (URISyntaxException e) {
+ throw new Error("Bad hardcoded URI", e);
+ }
+ }
+
+ private static SessionFactory sessionFactory;
+ private static TMService txService;
+ private static TransactionManager txManager;
+
+ public JotmTransactionStandaloneTest(String name) {
+ super(name);
+ }
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.addTest(new JotmTransactionStandaloneTest("setup"));
+ suite.addTest(new JotmTransactionStandaloneTest("testTrivalExplicit"));
+ suite.addTest(new JotmTransactionStandaloneTest("testSessionCloseRollback"));
+ suite.addTest(new JotmTransactionStandaloneTest("testTrivialExplicitAgain"));
+ suite.addTest(new JotmTransactionStandaloneTest("testBasicQuery"));
+ suite.addTest(new JotmTransactionStandaloneTest("testMultipleEnlist"));
+ suite.addTest(new JotmTransactionStandaloneTest("testMultipleQuery"));
+ suite.addTest(new JotmTransactionStandaloneTest("testBasicReadOnlyQuery"));
+ suite.addTest(new JotmTransactionStandaloneTest("testConcurrentQuery"));
+ suite.addTest(new JotmTransactionStandaloneTest("testRepeatGetXAQuery"));
+ suite.addTest(new JotmTransactionStandaloneTest("testConcurrentReadWrite"));
+ suite.addTest(new JotmTransactionStandaloneTest("testSubqueryQuery"));
+ suite.addTest(new JotmTransactionStandaloneTest("testTrivalImplicit"));
+ suite.addTest(new JotmTransactionStandaloneTest("cleanup"));
+
+ return suite;
+ }
+
+
+ public void setup() throws Exception {
+ logger.info("Doing setup");
+ sessionFactory = SessionFactoryFinder.newSessionFactory(databaseURI);
+ txService = new Jotm(true, false); // local, unbound.
+ txManager = txService.getTransactionManager();
+ }
+
+ public void cleanup() throws Exception {
+ logger.info("Doing cleanup");
+ txService.stop();
+ }
+
+ //
+ // Test cases
+ //
+
+ private static class TestXid implements Xid {
+ private int xid;
+ public TestXid(int xid) {
+ this.xid = xid;
+ }
+
+ public int getFormatId() {
+ return 'X';
+ }
+
+ public byte[] getBranchQualifier() {
+ return new byte[] {
+ (byte)(xid >> 0x00),
+ (byte)(xid >> 0x08)
+ };
+ }
+
+ public byte[] getGlobalTransactionId() {
+ return new byte[] {
+ (byte)(xid >> 0x10),
+ (byte)(xid >> 0x18)
+ };
+ }
+ }
+
+ /**
+ * Test the {@link DatabaseSession#create} method.
+ * As a side-effect, creates the model required by the next tests.
+ */
+ public void testTrivalExplicit() throws URISyntaxException {
+ logger.info("testTrivalExplicit");
+ try {
+ txManager.begin();
+ Session session = sessionFactory.newSession();
+ txManager.getTransaction().enlistResource(session.getXAResource());
+
+ try {
+ session.createModel(modelURI, null);
+ txManager.commit();
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+
+ public void testSessionCloseRollback() throws URISyntaxException {
+ logger.info("testSessionCloseRollback");
+ try {
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+ txManager.begin();
+ Session session = sessionFactory.newSession();
+ txManager.getTransaction().enlistResource(session.getXAResource());
+
+ try {
+ try {
+ session.setModel(modelURI, new ModelResource(fileURI));
+ } finally {
+ session.close();
+ }
+ } finally {
+ try {
+ txManager.commit();
+ } catch (HeuristicRollbackException eh) { // This is my expectation.
+ logger.warn("HeuristicRollback detected successfully", eh);
+ } catch (RollbackException er) { // This would also meet the spec.
+ logger.warn("Rollback detected successfully", er);
+ } catch (Exception e) {
+ logger.warn("Exception from Jotm", e);
+ throw e;
+ }
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testTrivialExplicitAgain() throws URISyntaxException {
+ logger.info("testTrivialExplicitAgain");
+ try {
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+ txManager.begin();
+ Session session = sessionFactory.newSession();
+ txManager.getTransaction().enlistResource(session.getXAResource());
+
+ try {
+ session.setModel(modelURI, new ModelResource(fileURI));
+ txManager.commit();
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testBasicQuery() throws URISyntaxException {
+ logger.info("testBasicQuery");
+
+ try {
+ txManager.begin();
+ Session session = sessionFactory.newSession();
+ try {
+ txManager.getTransaction().enlistResource(session.getXAResource());
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer);
+ answer.close();
+
+ txManager.commit();
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+
+ public void testMultipleEnlist() throws URISyntaxException {
+ logger.info("testMultipleEnlist");
+
+ try {
+ txManager.begin();
+ Session session = sessionFactory.newSession();
+ try {
+ txManager.getTransaction().enlistResource(session.getXAResource());
+ txManager.getTransaction().enlistResource(session.getXAResource());
+ txManager.getTransaction().enlistResource(session.getXAResource());
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer);
+ answer.close();
+
+ txManager.commit();
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+
+ public void testMultipleQuery() throws URISyntaxException {
+ logger.info("testMultipleQuery");
+
+ try {
+ txManager.begin();
+ Session session = sessionFactory.newSession();
+ txManager.getTransaction().enlistResource(session.getXAResource());
+ try {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer1 = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ Answer answer2 = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ compareResults(answer1, answer2);
+
+ answer1.close();
+ answer2.close();
+
+ txManager.commit();
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testBasicReadOnlyQuery() throws URISyntaxException {
+ logger.info("testBasicReadOnlyQuery");
+
+ try {
+ txManager.begin();
+ Session session = sessionFactory.newSession();
+ try {
+ txManager.getTransaction().enlistResource(session.getReadOnlyXAResource());
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer);
+ answer.close();
+
+ txManager.commit();
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testConcurrentQuery() throws URISyntaxException {
+ logger.info("testConcurrentQuery");
+
+ try {
+ txManager.begin();
+ Session session = sessionFactory.newSession();
+ XAResource roResource = session.getReadOnlyXAResource();
+ Transaction tx1 = txManager.getTransaction();
+ tx1.enlistResource(roResource);
+
+ try {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer1 = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ tx1 = txManager.suspend();
+
+ txManager.begin();
+ Transaction tx2 = txManager.getTransaction();
+ tx2.enlistResource(roResource);
+
+ Answer answer2 = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ tx2 = txManager.suspend();
+
+ compareResults(answer1, answer2);
+
+ answer1.close();
+ answer2.close();
+
+ txManager.resume(tx1);
+ txManager.commit();
+ // I believe JTA requires me to call end here - our implementation doesn't care.
+ tx2.commit();
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testRepeatGetXAQuery() throws URISyntaxException {
+ logger.info("testRepeatGetXAQuery");
+
+ try {
+ txManager.begin();
+ Session session = sessionFactory.newSession();
+ Transaction tx1 = txManager.getTransaction();
+ tx1.enlistResource(session.getReadOnlyXAResource());
+
+ try {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer1 = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ tx1 = txManager.suspend();
+
+ txManager.begin();
+ Transaction tx2 = txManager.getTransaction();
+ tx2.enlistResource(session.getReadOnlyXAResource());
+
+ Answer answer2 = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ tx2 = txManager.suspend();
+
+ compareResults(answer1, answer2);
+
+ answer1.close();
+ answer2.close();
+
+ txManager.resume(tx1);
+ txManager.commit();
+ // I believe JTA requires me to call end here - our implementation doesn't care.
+ tx2.commit();
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ //
+ // Note: What this test does is a really bad idea - there is no
+ // isolation provided as each operation is within its own
+ // transaction. It does however provide a good test.
+ //
+ public void testConcurrentReadWrite() throws URISyntaxException {
+ logger.info("testConcurrentReadWrite");
+
+ try {
+ txManager.begin();
+ Session session = sessionFactory.newSession();
+ XAResource roResource = session.getReadOnlyXAResource();
+ XAResource rwResource = session.getXAResource();
+
+ txManager.getTransaction().enlistResource(rwResource);
+ session.createModel(model2URI, null);
+ Transaction tx1 = txManager.suspend();
+
+ try {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ txManager.begin();
+ txManager.getTransaction().enlistResource(roResource);
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ Transaction tx2 = txManager.suspend();
+
+ answer.beforeFirst();
+ while (answer.next()) {
+ txManager.resume(tx1);
+ session.insert(model2URI, Collections.singleton(new TripleImpl(
+ (SubjectNode)answer.getObject(0),
+ (PredicateNode)answer.getObject(1),
+ (ObjectNode)answer.getObject(2))));
+ tx1 = txManager.suspend();
+ }
+ answer.close();
+
+ txManager.resume(tx1);
+ txManager.commit();
+
+ txManager.begin();
+ txManager.getTransaction().enlistResource(roResource);
+
+ Answer answer2 = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model2URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ Transaction tx3 = txManager.suspend();
+
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer2);
+ answer2.close();
+
+ txManager.begin();
+ txManager.getTransaction().enlistResource(rwResource);
+ session.removeModel(model2URI);
+ txManager.commit();
+
+ txManager.resume(tx2);
+ txManager.commit();
+ txManager.resume(tx3);
+ txManager.commit();
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testSubqueryQuery() throws URISyntaxException {
+ logger.info("testSubqueryQuery");
+
+ try {
+ txManager.begin();
+ Session session = sessionFactory.newSession();
+ txManager.getTransaction().enlistResource(session.getReadOnlyXAResource());
+
+ try {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(new Subquery(new Variable("k0"), new Query(
+ Collections.singletonList(objectVariable),
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(objectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ )));
+
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ new URIReferenceImpl(new URI("test:p03")),
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ Transaction tx1 = txManager.suspend();
+
+ answer.beforeFirst();
+
+ assertTrue(answer.next());
+ assertEquals(new URIReferenceImpl(new URI("test:s01")),
+ answer.getObject(0));
+ Answer sub1 = (Answer)answer.getObject(1);
+ compareResults(new String[][] { new String[] { "test:o01" },
+ new String[] { "test:o02" } }, sub1);
+ sub1.close();
+
+ assertTrue(answer.next());
+ assertEquals(new URIReferenceImpl(new URI("test:s02")),
+ answer.getObject(0));
+ Answer sub2 = (Answer)answer.getObject(1);
+ compareResults(new String[][] { new String[] { "test:o02" },
+ new String[] { "test:o03" } }, sub2);
+ // Leave sub2 open.
+
+ assertFalse(answer.next());
+ answer.close();
+ sub2.close();
+
+ // Leave transaction to be closed on session close.
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+/*
+
+ public void testConcurrentSubqueryQuery() throws URISyntaxException {
+ logger.info("testConcurrentSubqueryQuery");
+
+ try {
+ Session session = database.newSession();
+ XAResource rwResource = session.getXAResource();
+ Xid xid1 = new TestXid(1);
+ rwResource.start(xid1, XAResource.TMNOFLAGS);
+
+ try {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(new Subquery(new Variable("k0"), new Query(
+ Collections.singletonList(objectVariable),
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(objectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ )));
+
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(modelURI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ new URIReferenceImpl(new URI("test:p03")),
+ objectVariable),
+ null, // HAVING
+ Collections.singletonList( // ORDER BY
+ new Order(subjectVariable, true)
+ ),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ answer.beforeFirst();
+
+ assertTrue(answer.next());
+ assertEquals(new URIReferenceImpl(new URI("test:s01")),
+ answer.getObject(0));
+ Answer sub1 = (Answer)answer.getObject(1);
+ assertTrue(answer.next());
+ assertEquals(new URIReferenceImpl(new URI("test:s02")),
+ answer.getObject(0));
+ Answer sub2 = (Answer)answer.getObject(1);
+ assertFalse(answer.next());
+
+ assertEquals(1, sub1.getNumberOfVariables());
+ assertEquals(1, sub2.getNumberOfVariables());
+ sub1.beforeFirst();
+ sub2.beforeFirst();
+ assertTrue(sub1.next());
+ assertTrue(sub2.next());
+ assertEquals(new URIReferenceImpl(new URI("test:o01")), sub1.getObject(0));
+ assertEquals(new URIReferenceImpl(new URI("test:o02")), sub2.getObject(0));
+
+ rwResource.end(xid1, XAResource.TMSUSPEND);
+
+ assertTrue(sub1.next());
+ assertTrue(sub2.next());
+ assertEquals(new URIReferenceImpl(new URI("test:o02")), sub1.getObject(0));
+ assertEquals(new URIReferenceImpl(new URI("test:o03")), sub2.getObject(0));
+ assertFalse(sub1.next());
+ assertFalse(sub2.next());
+
+ answer.close();
+
+ rwResource.end(xid1, XAResource.TMSUCCESS);
+ rwResource.commit(xid1, true);
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testExplicitIsolationQuerySingleSession() throws URISyntaxException
+ {
+ logger.info("testExplicitIsolationQuery");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session = database.newSession();
+ try {
+ XAResource roResource = session.getReadOnlyXAResource();
+ XAResource rwResource = session.getXAResource();
+ Xid xid1 = new TestXid(1); // Initial create model.
+ Xid xid2 = new TestXid(2); // Started before setModel.
+ Xid xid3 = new TestXid(3); // setModel.
+ Xid xid4 = new TestXid(4); // Started before setModel prepares
+ Xid xid5 = new TestXid(5); // Started before setModel commits
+ Xid xid6 = new TestXid(6); // Started after setModel commits
+ Xid xid7 = new TestXid(7); // Final remove model.
+
+ rwResource.start(xid1, XAResource.TMNOFLAGS);
+ session.createModel(model3URI, null);
+ rwResource.end(xid1, XAResource.TMSUCCESS);
+ rwResource.commit(xid1, true);
+
+ // Nothing visible.
+ roResource.start(xid2, XAResource.TMNOFLAGS);
+ assertChangeNotVisible(session);
+ roResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Perform update
+ rwResource.start(xid3, XAResource.TMNOFLAGS);
+ session.setModel(model3URI, new ModelResource(fileURI));
+ rwResource.end(xid3, XAResource.TMSUSPEND);
+
+ // Check uncommitted change not visible
+ roResource.start(xid4, XAResource.TMNOFLAGS);
+ assertChangeNotVisible(session);
+ roResource.end(xid4, XAResource.TMSUSPEND);
+
+ // Check original phase unaffected.
+ roResource.start(xid2, XAResource.TMRESUME);
+ assertChangeNotVisible(session);
+ roResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Check micro-commit visible to current-phase
+ rwResource.start(xid3, XAResource.TMRESUME);
+ assertChangeVisible(session);
+ // Perform prepare
+ rwResource.end(xid3, XAResource.TMSUCCESS);
+ rwResource.prepare(xid3);
+
+ // Check original phase unaffected
+ roResource.start(xid2, XAResource.TMRESUME);
+ assertChangeNotVisible(session);
+ roResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Check pre-prepare phase unaffected
+ roResource.start(xid4, XAResource.TMRESUME);
+ assertChangeNotVisible(session);
+ roResource.end(xid4, XAResource.TMSUSPEND);
+
+ // Check committed phase unaffected.
+ roResource.start(xid5, XAResource.TMNOFLAGS);
+ assertChangeNotVisible(session);
+ roResource.end(xid5, XAResource.TMSUSPEND);
+
+ // Do commit
+ rwResource.commit(xid3, false);
+
+ // Check original phase
+ roResource.start(xid2, XAResource.TMRESUME);
+ assertChangeNotVisible(session);
+ roResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Check pre-prepare
+ roResource.start(xid4, XAResource.TMRESUME);
+ assertChangeNotVisible(session);
+ roResource.end(xid4, XAResource.TMSUSPEND);
+
+ // Check pre-commit
+ roResource.start(xid5, XAResource.TMRESUME);
+ assertChangeNotVisible(session);
+ roResource.end(xid5, XAResource.TMSUSPEND);
+
+ // Check committed phase is now updated
+ roResource.start(xid6, XAResource.TMNOFLAGS);
+ assertChangeVisible(session);
+
+ // Cleanup transactions.
+ roResource.end(xid6, XAResource.TMSUCCESS);
+ roResource.end(xid2, XAResource.TMSUCCESS);
+ roResource.end(xid4, XAResource.TMSUCCESS);
+ roResource.end(xid5, XAResource.TMSUCCESS);
+ roResource.commit(xid2, true);
+ roResource.commit(xid4, true);
+ roResource.commit(xid5, true);
+ roResource.commit(xid6, true);
+
+ // Cleanup database
+ rwResource.start(xid7, XAResource.TMNOFLAGS);
+ session.removeModel(model3URI);
+ rwResource.end(xid7, XAResource.TMSUCCESS);
+ rwResource.commit(xid7, true);
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testExternalInternalIsolation() throws URISyntaxException
+ {
+ logger.info("testExplicitIsolationQuery");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ Session session2 = database.newSession();
+ try {
+ XAResource roResource = session1.getReadOnlyXAResource();
+ XAResource rwResource = session1.getXAResource();
+ Xid xid1 = new TestXid(1); // Initial create model.
+ Xid xid2 = new TestXid(2); // Main Test.
+ Xid xid3 = new TestXid(3); // Cleanup test.
+
+ rwResource.start(xid1, XAResource.TMNOFLAGS);
+ session1.createModel(model3URI, null);
+ rwResource.end(xid1, XAResource.TMSUCCESS);
+ rwResource.commit(xid1, true);
+
+ // Nothing visible.
+ assertChangeNotVisible(session2);
+
+ // Perform update
+ rwResource.start(xid2, XAResource.TMNOFLAGS);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+ rwResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Check uncommitted change not visible
+ assertChangeNotVisible(session2);
+
+ // Check micro-commit visible to current-phase
+ rwResource.start(xid2, XAResource.TMRESUME);
+ assertChangeVisible(session1);
+ // Perform prepare
+ rwResource.end(xid2, XAResource.TMSUCCESS);
+ rwResource.prepare(xid2);
+
+ // Check original phase unaffected
+ assertChangeNotVisible(session2);
+
+ // Do commit
+ rwResource.commit(xid2, false);
+
+ // Check committed phase is now updated
+ assertChangeVisible(session2);
+
+ // Cleanup database
+ session2.removeModel(model3URI);
+ } finally {
+ session2.close();
+ }
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ public void testInternalExternalIsolation() throws URISyntaxException
+ {
+ logger.info("testExplicitIsolationQuery");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ Session session2 = database.newSession();
+ try {
+ XAResource roResource = session2.getReadOnlyXAResource();
+ XAResource rwResource = session2.getXAResource();
+ Xid xid1 = new TestXid(1); // Pre-update
+ Xid xid2 = new TestXid(2); // Post-update/Pre-commit
+ Xid xid3 = new TestXid(3); // Post-commit
+
+ session1.createModel(model3URI, null);
+
+ // Nothing visible.
+ roResource.start(xid1, XAResource.TMNOFLAGS);
+ assertChangeNotVisible(session2);
+ roResource.end(xid1, XAResource.TMSUSPEND);
+
+ // Perform update with autocommit off
+ session1.setAutoCommit(false);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+
+ // Check uncommitted change not visible
+ roResource.start(xid2, XAResource.TMNOFLAGS);
+ assertChangeNotVisible(session2);
+ roResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Check original phase unaffected.
+ roResource.start(xid1, XAResource.TMRESUME);
+ assertChangeNotVisible(session2);
+ roResource.end(xid1, XAResource.TMSUSPEND);
+
+ // Check micro-commit visible to current-phase
+ assertChangeVisible(session1);
+ session1.setAutoCommit(true);
+
+ // Check original phase unaffected
+ roResource.start(xid1, XAResource.TMRESUME);
+ assertChangeNotVisible(session2);
+ roResource.end(xid1, XAResource.TMSUSPEND);
+
+ // Check pre-commit phase unaffected
+ roResource.start(xid2, XAResource.TMRESUME);
+ assertChangeNotVisible(session2);
+ roResource.end(xid2, XAResource.TMSUSPEND);
+
+ // Check committed phase is now updated and write-lock available
+ rwResource.start(xid3, XAResource.TMNOFLAGS);
+ assertChangeVisible(session2);
+
+ // Check internal transaction read-only
+ assertChangeVisible(session1);
+
+ // Cleanup transactions.
+ rwResource.end(xid3, XAResource.TMSUCCESS);
+ roResource.end(xid2, XAResource.TMSUCCESS);
+ roResource.end(xid1, XAResource.TMSUCCESS);
+ roResource.commit(xid1, true);
+ roResource.commit(xid2, true);
+ rwResource.commit(xid3, true);
+
+ // Cleanup database (check write-lock available again)
+ session1.removeModel(model3URI);
+ } finally {
+ session2.close();
+ }
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ private void assertChangeVisible(Session session) throws Exception {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer);
+ answer.close();
+ }
+
+ private void assertChangeNotVisible(Session session) throws Exception {
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+ answer.beforeFirst();
+ assertFalse(answer.next());
+ answer.close();
+ }
+
+ //
+ // Test two simultaneous, explicit transactions, in two threads. The second one should block
+ // until the first one sets auto-commit back to true.
+ //
+ public void testConcurrentExplicitTxn() throws URISyntaxException
+ {
+ logger.info("testConcurrentExplicitTxn");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ XAResource resource1 = session1.getXAResource();
+ resource1.start(new TestXid(1), XAResource.TMNOFLAGS);
+ session1.createModel(model3URI, null);
+ resource1.end(new TestXid(1), XAResource.TMSUCCESS);
+ resource1.commit(new TestXid(1), true);
+
+ resource1.start(new TestXid(2), XAResource.TMNOFLAGS);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+
+ final boolean[] tx2Started = new boolean[] { false };
+
+ Thread t2 = new Thread("tx2Test") {
+ public void run() {
+ try {
+ Session session2 = database.newSession();
+ XAResource resource2 = session2.getXAResource();
+ try {
+ resource2.start(new TestXid(3), XAResource.TMNOFLAGS);
+
+ synchronized (tx2Started) {
+ tx2Started[0] = true;
+ tx2Started.notify();
+ }
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session2.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer);
+ answer.close();
+
+ resource2.end(new TestXid(3), XAResource.TMSUCCESS);
+ resource2.commit(new TestXid(3), true);
+ } finally {
+ session2.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+ };
+ t2.start();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ resource1.commit(new TestXid(2), true);
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ assertTrue("second transaction should've started", tx2Started[0]);
+ }
+ }
+
+ try {
+ t2.join(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-terminated interrupted", ie);
+ fail(ie);
+ }
+ assertFalse("second transaction should've terminated", t2.isAlive());
+
+ resource1.start(new TestXid(4), XAResource.TMNOFLAGS);
+ session1.removeModel(model3URI);
+ resource1.end(new TestXid(4), XAResource.TMSUCCESS);
+ resource1.commit(new TestXid(4), true);
+
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ //*
+ // Test two simultaneous transactions, in two threads. The second one should block
+ // until the first one sets auto-commit back to true.
+ ///
+ public void testExternalInternalConcurrentTxn() throws URISyntaxException
+ {
+ logger.info("testExternalInternalConcurrentTxn");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ XAResource resource1 = session1.getXAResource();
+ resource1.start(new TestXid(1), XAResource.TMNOFLAGS);
+ session1.createModel(model3URI, null);
+ resource1.end(new TestXid(1), XAResource.TMSUCCESS);
+ resource1.commit(new TestXid(1), true);
+
+ resource1.start(new TestXid(2), XAResource.TMNOFLAGS);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+
+ final boolean[] tx2Started = new boolean[] { false };
+
+ Thread t2 = new Thread("tx2Test") {
+ public void run() {
+ try {
+ Session session2 = database.newSession();
+ try {
+ session2.setAutoCommit(false);
+
+ synchronized (tx2Started) {
+ tx2Started[0] = true;
+ tx2Started.notify();
+ }
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session2.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer);
+ answer.close();
+
+ session2.setAutoCommit(true);
+ } finally {
+ session2.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+ };
+ t2.start();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ resource1.commit(new TestXid(2), true);
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ assertTrue("second transaction should've started", tx2Started[0]);
+ }
+ }
+
+ try {
+ t2.join(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-terminated interrupted", ie);
+ fail(ie);
+ }
+ assertFalse("second transaction should've terminated", t2.isAlive());
+
+ resource1.start(new TestXid(4), XAResource.TMNOFLAGS);
+ session1.removeModel(model3URI);
+ resource1.end(new TestXid(4), XAResource.TMSUCCESS);
+ resource1.commit(new TestXid(4), true);
+
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+
+ //*
+ // Test two simultaneous transactions, in two threads. The second one should block
+ // until the first one sets auto-commit back to true.
+ ///
+ public void testInternalExternalConcurrentTxn() throws URISyntaxException
+ {
+ logger.info("testInternalExternalConcurrentTxn");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ session1.createModel(model3URI, null);
+
+ session1.setAutoCommit(false);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+
+ final boolean[] tx2Started = new boolean[] { false };
+
+ Thread t2 = new Thread("tx2Test") {
+ public void run() {
+ try {
+ Session session2 = database.newSession();
+ try {
+ XAResource resource = session2.getXAResource();
+ resource.start(new TestXid(1), XAResource.TMNOFLAGS);
+
+ synchronized (tx2Started) {
+ tx2Started[0] = true;
+ tx2Started.notify();
+ }
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session2.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ String[][] results = {
+ { "test:s01", "test:p01", "test:o01" },
+ { "test:s01", "test:p02", "test:o01" },
+ { "test:s01", "test:p02", "test:o02" },
+ { "test:s01", "test:p03", "test:o02" },
+ { "test:s02", "test:p03", "test:o02" },
+ { "test:s02", "test:p04", "test:o02" },
+ { "test:s02", "test:p04", "test:o03" },
+ { "test:s02", "test:p05", "test:o03" },
+ { "test:s03", "test:p01", "test:o01" },
+ { "test:s03", "test:p05", "test:o03" },
+ { "test:s03", "test:p06", "test:o01" },
+ { "test:s03", "test:p06", "test:o03" },
+ };
+ compareResults(results, answer);
+ answer.close();
+
+ resource.end(new TestXid(1), XAResource.TMSUCCESS);
+ resource.rollback(new TestXid(1));
+ } finally {
+ session2.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+ };
+ t2.start();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ session1.commit();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ session1.setAutoCommit(true);
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ assertTrue("second transaction should've started", tx2Started[0]);
+ }
+ }
+
+ try {
+ t2.join(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-terminated interrupted", ie);
+ fail(ie);
+ }
+ assertFalse("second transaction should've terminated", t2.isAlive());
+
+ session1.removeModel(model3URI);
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ //*
+ // Test two simultaneous transactions, in two threads. The second one should block
+ // until the first one sets auto-commit back to true.
+ ///
+ public void testExternalInternalConcurrentTxnRollback() throws URISyntaxException
+ {
+ logger.info("testExternalInternalConcurrentTxnRollback");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ XAResource resource1 = session1.getXAResource();
+ resource1.start(new TestXid(1), XAResource.TMNOFLAGS);
+ session1.createModel(model3URI, null);
+ resource1.end(new TestXid(1), XAResource.TMSUCCESS);
+ resource1.commit(new TestXid(1), true);
+
+ resource1.start(new TestXid(2), XAResource.TMNOFLAGS);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+
+ final boolean[] tx2Started = new boolean[] { false };
+
+ Thread t2 = new Thread("tx2Test") {
+ public void run() {
+ try {
+ Session session2 = database.newSession();
+ try {
+ session2.setAutoCommit(false);
+
+ synchronized (tx2Started) {
+ tx2Started[0] = true;
+ tx2Started.notify();
+ }
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session2.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ answer.beforeFirst();
+ assertFalse(answer.next());
+ answer.close();
+
+ session2.setAutoCommit(true);
+ } finally {
+ session2.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+ };
+ t2.start();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ resource1.rollback(new TestXid(2));
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ assertTrue("second transaction should've started", tx2Started[0]);
+ }
+ }
+
+ try {
+ t2.join(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-terminated interrupted", ie);
+ fail(ie);
+ }
+ assertFalse("second transaction should've terminated", t2.isAlive());
+
+ resource1.start(new TestXid(4), XAResource.TMNOFLAGS);
+ session1.removeModel(model3URI);
+ resource1.end(new TestXid(4), XAResource.TMSUCCESS);
+ resource1.commit(new TestXid(4), true);
+
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+
+ //*
+ // Test two simultaneous transactions, in two threads. The second one should block
+ // until the first one sets auto-commit back to true.
+ ///
+ public void testInternalExternalConcurrentTxnRollback() throws URISyntaxException
+ {
+ logger.info("testInternalExternalConcurrentTxnRollback");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session1 = database.newSession();
+ try {
+ session1.createModel(model3URI, null);
+
+ session1.setAutoCommit(false);
+ session1.setModel(model3URI, new ModelResource(fileURI));
+
+ final boolean[] tx2Started = new boolean[] { false };
+
+ Thread t2 = new Thread("tx2Test") {
+ public void run() {
+ try {
+ Session session2 = database.newSession();
+ try {
+ XAResource resource = session2.getXAResource();
+ resource.start(new TestXid(1), XAResource.TMNOFLAGS);
+
+ synchronized (tx2Started) {
+ tx2Started[0] = true;
+ tx2Started.notify();
+ }
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session2.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ answer.beforeFirst();
+ assertFalse(answer.next());
+ answer.close();
+
+ resource.end(new TestXid(1), XAResource.TMFAIL);
+ resource.rollback(new TestXid(1));
+ } finally {
+ session2.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+ };
+ t2.start();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ session1.rollback();
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ }
+ assertFalse("second transaction should still be waiting for write lock", tx2Started[0]);
+ }
+
+ session1.setAutoCommit(true);
+
+ synchronized (tx2Started) {
+ if (!tx2Started[0]) {
+ try {
+ tx2Started.wait(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-started interrupted", ie);
+ fail(ie);
+ }
+ assertTrue("second transaction should've started", tx2Started[0]);
+ }
+ }
+
+ try {
+ t2.join(2000L);
+ } catch (InterruptedException ie) {
+ logger.error("wait for tx2-terminated interrupted", ie);
+ fail(ie);
+ }
+ assertFalse("second transaction should've terminated", t2.isAlive());
+
+ session1.removeModel(model3URI);
+ } finally {
+ session1.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+
+ public void testExplicitRollbackIsolationQuery() throws URISyntaxException {
+ logger.info("testExplicitRollbackIsolationQuery");
+ URI fileURI = new File("data/xatest-model1.rdf").toURI();
+
+ try {
+ Session session = database.newSession();
+ XAResource roResource = session.getReadOnlyXAResource();
+ XAResource rwResource = session.getXAResource();
+ try {
+ rwResource.start(new TestXid(1), XAResource.TMNOFLAGS);
+ session.createModel(model3URI, null);
+ rwResource.end(new TestXid(1), XAResource.TMSUCCESS);
+ rwResource.commit(new TestXid(1), true);
+
+ rwResource.start(new TestXid(2), XAResource.TMNOFLAGS);
+ session.setModel(model3URI, new ModelResource(fileURI));
+ rwResource.end(new TestXid(2), XAResource.TMSUSPEND);
+
+ roResource.start(new TestXid(3), XAResource.TMNOFLAGS);
+
+ Variable subjectVariable = new Variable("subject");
+ Variable predicateVariable = new Variable("predicate");
+ Variable objectVariable = new Variable("object");
+
+ List selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ Answer answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+ answer.beforeFirst();
+ assertFalse(answer.next());
+ answer.close();
+
+ roResource.end(new TestXid(3), XAResource.TMSUCCESS);
+ roResource.commit(new TestXid(3), true);
+
+ rwResource.end(new TestXid(2), XAResource.TMFAIL);
+ rwResource.rollback(new TestXid(2));
+
+ roResource.start(new TestXid(4), XAResource.TMNOFLAGS);
+ selectList = new ArrayList(3);
+ selectList.add(subjectVariable);
+ selectList.add(predicateVariable);
+ selectList.add(objectVariable);
+
+ // Evaluate the query
+ answer = session.query(new Query(
+ selectList, // SELECT
+ new ModelResource(model3URI), // FROM
+ new ConstraintImpl(subjectVariable, // WHERE
+ predicateVariable,
+ objectVariable),
+ null, // HAVING
+ Arrays.asList(new Order[] { // ORDER BY
+ new Order(subjectVariable, true),
+ new Order(predicateVariable, true),
+ new Order(objectVariable, true)
+ }),
+ null, // LIMIT
+ 0, // OFFSET
+ new UnconstrainedAnswer() // GIVEN
+ ));
+
+ answer.beforeFirst();
+ assertFalse(answer.next());
+ answer.close();
+
+ roResource.end(new TestXid(4), XAResource.TMFAIL);
+ roResource.rollback(new TestXid(4));
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+*/
+
+ public void testTrivalImplicit() throws URISyntaxException
+ {
+ logger.info("testTrivialImplicit");
+ try {
+ Session session = sessionFactory.newSession();
+
+ try {
+ session.removeModel(modelURI);
+ } finally {
+ session.close();
+ }
+ } catch (Exception e) {
+ fail(e);
+ }
+ }
+
+ //
+ // Internal methods
+ //
+
+ private void compareResults(String[][] expected, Answer answer) throws Exception {
+ try {
+ answer.beforeFirst();
+ for (int i = 0; i < expected.length; i++) {
+ assertTrue("Answer short at row " + i, answer.next());
+ assertEquals(expected[i].length, answer.getNumberOfVariables());
+ for (int j = 0; j < expected[i].length; j++) {
+ URIReferenceImpl uri = new URIReferenceImpl(new URI(expected[i][j]));
+ assertEquals(uri, answer.getObject(j));
+ }
+ }
+ assertFalse(answer.next());
+ } catch (Exception e) {
+ logger.info("Failed test - " + answer);
+ answer.close();
+ throw e;
+ }
+ }
+
+ private void compareResults(Answer answer1, Answer answer2) throws Exception {
+ answer1.beforeFirst();
+ answer2.beforeFirst();
+ assertEquals(answer1.getNumberOfVariables(), answer2.getNumberOfVariables());
+ while (answer1.next()) {
+ assertTrue(answer2.next());
+ for (int i = 0; i < answer1.getNumberOfVariables(); i++) {
+ assertEquals(answer1.getObject(i), answer2.getObject(i));
+ }
+ }
+ assertFalse(answer2.next());
+ }
+
+ /**
+ * Fail with an unexpected exception
+ */
+ private void fail(Throwable throwable)
+ {
+ StringWriter stringWriter = new StringWriter();
+ throwable.printStackTrace(new PrintWriter(stringWriter));
+ fail(stringWriter.toString());
+ }
+
+ private static class DummyXAResource implements XAResource {
+ public void end(Xid xid, int flags) throws XAException {}
+ public void forget(Xid xid) throws XAException {}
+ public int getTransactionTimeout() throws XAException { return 0; }
+ public int prepare(Xid xid) throws XAException { return 0; }
+ public Xid[] recover(int flag) throws XAException { return new Xid[] {}; }
+ public void rollback(Xid xid) throws XAException {}
+ public boolean setTransactionTimeout(int seconds) throws XAException { return false; }
+ public void start(Xid xid, int flags) throws XAException {}
+ public void commit(Xid xid, boolean twophase) throws XAException {}
+ public boolean isSameRM(XAResource xa) { return xa == this; }
+ }
+}
Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/LocalJRDFDatabaseSession.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -191,7 +191,7 @@
constraint = appendMulgaraIsConstraint(vars[2], toValue(object), constraint);
Query query = new Query(
- Arrays.asList((Object[])vars), // variable list
+ Arrays.asList(vars), // variable list
new ModelResource(modelURI), // model expression
constraint, // constraint expr
null, // no having
Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/ModifyModelOperation.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -85,7 +85,7 @@
* If this field is not <code>null</code>, {@link #query} will be
* <code>null</code>
*/
- private final Set<Triple> tripleSet;
+ private final Set<? extends Triple> tripleSet;
/**
* The query generating the statements whose occurence is to be modified.
@@ -112,7 +112,7 @@
* @throws IllegalArgumentException if <var>modelURI</var> or
* <var>tripleSet</var> are <code>null</code>
*/
- ModifyModelOperation(URI modelURI, Set<Triple> tripleSet, boolean insert)
+ ModifyModelOperation(URI modelURI, Set<? extends Triple> tripleSet, boolean insert)
{
// Validate "modelURI" parameter
if (modelURI == null) {
Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransaction.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,332 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com) under contract to
+ * Topaz Foundation. Portions created under this contract are
+ * Copyright (c) 2007 Topaz Foundation
+ * All Rights Reserved.
+ */
+package org.mulgara.resolver;
+
+// Java 2 enterprise packages
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import javax.transaction.xa.XAException;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+// Local packages
+import org.mulgara.resolver.spi.DatabaseMetadata;
+import org.mulgara.resolver.spi.EnlistableResource;
+import org.mulgara.resolver.spi.ResolverSessionFactory;
+
+import org.mulgara.query.MulgaraTransactionException;
+import org.mulgara.query.TuplesException;
+import org.mulgara.query.QueryException;
+
+/**
+ * @created 2007-11-06
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <a href="mailto:mail at netymon.com">Netymon Pty Ltd</a>
+ *
+ * @copyright ©2007 <a href="http://www.topazproject.org/">Topaz Foundation</a>
+ *
+ * @licence Open Software License v3.0
+ */
+public class MulgaraExternalTransaction implements MulgaraTransaction {
+ private static final Logger logger =
+ Logger.getLogger(MulgaraExternalTransaction.class.getName());
+
+ private Xid xid;
+
+ private Set<EnlistableResource> enlisted;
+ private Set<EnlistableResource> prepared;
+ private Set<EnlistableResource> committed;
+ private Set<EnlistableResource> rollbacked;
+
+ private Map<EnlistableResource, XAResource> xaResources;
+
+ private MulgaraExternalTransactionFactory factory;
+ private DatabaseOperationContext context;
+
+ private boolean hRollback;
+ private int heurCode;
+ private boolean rollback;
+
+ MulgaraExternalTransaction(MulgaraExternalTransactionFactory factory, Xid xid, DatabaseOperationContext context)
+ throws QueryException {
+ this.factory = factory;
+ this.context = context;
+ this.xid = xid;
+
+ this.enlisted = new HashSet<EnlistableResource>();
+ this.prepared = new HashSet<EnlistableResource>();
+ this.committed = new HashSet<EnlistableResource>();
+ this.rollbacked = new HashSet<EnlistableResource>();
+
+ this.xaResources = new HashMap<EnlistableResource, XAResource>();
+
+ this.hRollback = false;
+ this.heurCode = 0;
+ this.rollback = false;
+
+ this.context.initiate(this);
+ }
+
+ // We ignore reference counting in external transactions
+ public void reference() throws MulgaraTransactionException {}
+ public void dereference() throws MulgaraTransactionException {}
+
+ public MulgaraTransactionException abortTransaction(String errorMessage, Throwable cause)
+ throws MulgaraTransactionException {
+ report("abortTransaction");
+ try {
+ for (EnlistableResource resource : enlisted) {
+ try {
+ resource.abort();
+ } catch (Throwable throw_away) {}
+ }
+ for (EnlistableResource resource : prepared) {
+ try {
+ resource.abort();
+ } catch (Throwable throw_away) {}
+ }
+
+ return new MulgaraTransactionException(errorMessage, cause);
+ } finally {
+ factory.transactionComplete(this);
+ }
+ }
+
+ public void heuristicRollback(String cause) throws MulgaraTransactionException {
+ report("heuristicRollback");
+ hRollback = true;
+ try {
+ rollback(xid);
+ } catch (XAException xa) {
+ throw new MulgaraTransactionException("Failed heuristic rollback", xa);
+ } finally {
+ heurCode = heurCode == 0 ? XAException.XA_HEURRB : heurCode;
+ }
+ }
+
+ public void execute(Operation operation,
+ ResolverSessionFactory resolverSessionFactory,
+ DatabaseMetadata metadata) throws MulgaraTransactionException {
+ // FIXME: Do I need to check that this transaction is 'active' ?
+ try {
+ operation.execute(context,
+ context.getSystemResolver(),
+ resolverSessionFactory,
+ metadata);
+ } catch (Throwable th) {
+ try {
+ rollback(xid);
+ } catch (XAException ex) {
+ logger.error("Error in rollback after operation failure", ex);
+ }
+ throw new MulgaraTransactionException("Operation failed", th);
+ }
+ }
+
+ public AnswerOperationResult execute(AnswerOperation ao) throws TuplesException {
+ try {
+ ao.execute();
+ return ao.getResult();
+ } catch (Throwable th) {
+ try {
+ logger.warn("Error in answer operation triggered rollback", th);
+ rollback(xid);
+ } catch (XAException ex) {
+ logger.error("Error in rollback after answer-operation failure", ex);
+ }
+ throw new TuplesException("Request failed", th);
+ }
+ }
+
+ // FIXME: See if we can't rearrange things to allow this to be deleted.
+ public void execute(TransactionOperation to) throws MulgaraTransactionException {
+ to.execute();
+ }
+
+ public void enlist(EnlistableResource enlistable) throws MulgaraTransactionException {
+ try {
+ XAResource res = enlistable.getXAResource();
+ for (EnlistableResource eres : enlisted) {
+ if (res.isSameRM(xaResources.get(eres))) {
+ return;
+ }
+ }
+ enlisted.add(enlistable);
+ xaResources.put(enlistable, res);
+ // FIXME: We need to handle this uptodate operation properly - handle
+ // suspension or mid-prepare/commit.
+ // bringUptodate(res);
+ res.start(xid, XAResource.TMNOFLAGS);
+ } catch (XAException ex) {
+ throw new MulgaraTransactionException("Failed to enlist resource", ex);
+ }
+ }
+
+ //
+ // Methods used to manage transaction from XAResource.
+ //
+
+ void commit(Xid xid) throws XAException {
+ report("commit");
+ // FIXME: Consider the possiblity prepare failed, or was incomplete.
+ for (EnlistableResource er : prepared) {
+ xaResources.get(er).commit(xid, false);
+ committed.add(er);
+ }
+ cleanupTransaction();
+ }
+
+ boolean isHeuristicallyRollbacked() {
+ return hRollback;
+ }
+
+ boolean isHeuristicallyCommitted() {
+ return false;
+ }
+
+ int getHeuristicCode() {
+ return heurCode;
+ }
+
+ boolean isRollbacked() {
+ return rollback;
+ }
+
+ void prepare(Xid xid) throws XAException {
+ report("prepare");
+ for (EnlistableResource er : enlisted) {
+ xaResources.get(er).prepare(xid);
+ prepared.add(er);
+ }
+ // status = PREPARED; ?
+ }
+
+ /**
+ * Perform rollback. Only throws exception if transaction is subject to
+ * Heuristic Completion.
+ */
+ void rollback(Xid xid) throws XAException {
+ report("rollback");
+ try {
+ rollback = true;
+ Map<EnlistableResource, XAException> rollbackFailed = new HashMap<EnlistableResource, XAException>();
+
+ for (EnlistableResource er : enlisted) {
+ try {
+ if (!committed.contains(er)) {
+ xaResources.get(er).rollback(xid);
+ rollbacked.add(er);
+ }
+ } catch (XAException ex) {
+ logger.error("Attempt to rollback resource failed", ex);
+ rollbackFailed.put(er, ex);
+ }
+ }
+
+ if (rollbackFailed.isEmpty()) {
+ if (committed.isEmpty()) { // Clean failure and rollback
+ return; // SUCCESSFUL ROLLBACK - RETURN
+ } else { // No rollback-failure, but partial commit
+ heurCode = XAException.XA_HEURMIX;
+ throw new XAException(heurCode);
+ }
+ } else {
+ // Something went wrong - start by assuming if one committed all committed
+ heurCode = (committed.isEmpty()) ? 0 : XAException.XA_HEURCOM;
+ // Then check every rollback failure code for a contradiction to all committed.
+ for (XAException xaex : rollbackFailed.values()) {
+ switch (xaex.errorCode) {
+ case XAException.XA_HEURHAZ:
+ case XAException.XAER_NOTA:
+ case XAException.XAER_RMERR:
+ case XAException.XAER_RMFAIL:
+ case XAException.XAER_INVAL:
+ case XAException.XAER_PROTO:
+ // All these amount to not knowing the result - so we have a hazard
+ // unless we already know we have a mixed result.
+ if (heurCode != XAException.XA_HEURMIX) {
+ heurCode = XAException.XA_HEURHAZ;
+ }
+ break;
+ case XAException.XA_HEURCOM:
+ if (!rollbacked.isEmpty() || heurCode == XAException.XA_HEURRB) {
+ // We know something else was rollbacked, so we know we have a mixed result.
+ heurCode = XAException.XA_HEURMIX;
+ } else if (heurCode == 0) {
+ heurCode = XAException.XA_HEURCOM;
+ } // else it's a HEURHAZ or a HEURCOM and stays that way.
+ break;
+ case XAException.XA_HEURRB:
+ if (!committed.isEmpty() || heurCode == XAException.XA_HEURCOM) {
+ heurCode = XAException.XA_HEURMIX;
+ } else if (heurCode == 0) {
+ heurCode = XAException.XA_HEURRB;
+ } // else it's a HEURHAZ or a HEURRB and stays that way.
+ break;
+ case XAException.XA_HEURMIX:
+ // It can't get worse than, we know we have a mixed result.
+ heurCode = XAException.XA_HEURMIX;
+ break;
+ default:
+ // The codes above are the only codes permitted from a rollback() so
+ // anything else indicates a serious error in the resource-manager.
+ throw new XAException(XAException.XAER_RMERR);
+ }
+ }
+
+ throw new XAException(heurCode);
+ }
+ } finally {
+ cleanupTransaction();
+ }
+ }
+
+
+ Xid getXid() {
+ return xid;
+ }
+
+ private void cleanupTransaction() throws XAException {
+ report("cleanupTransaction");
+ try {
+ factory.transactionComplete(this);
+ } catch (MulgaraTransactionException em) {
+ try {
+ logger.error("Failed to cleanup transaction", em);
+ abortTransaction("Failure in cleanup", em);
+ throw new XAException(XAException.XAER_RMERR);
+ } catch (MulgaraTransactionException em2) {
+ logger.error("Failed to abort transaction on cleanup failure", em2);
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+ }
+
+ private void report(String desc) {
+ if (logger.isInfoEnabled()) {
+ logger.info(desc + ": " + System.identityHashCode(this));
+ }
+ }
+}
Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransactionFactory.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransactionFactory.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransactionFactory.java (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraExternalTransactionFactory.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,243 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com) under contract to
+ * Topaz Foundation. Portions created under this contract are
+ * Copyright (c) 2007 Topaz Foundation
+ * All Rights Reserved.
+ */
+
+package org.mulgara.resolver;
+
+// Java2 packages
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+// Local packages
+import org.mulgara.query.MulgaraTransactionException;
+import org.mulgara.query.QueryException;
+import org.mulgara.transaction.TransactionManagerFactory;
+import org.mulgara.util.Assoc1toNMap;
+
+/**
+ * Manages external transactions.
+ *
+ * @created 2007-11-06
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ *
+ * @copyright ©2007 <a href="http://www.topazproject.org/">Topaz Foundation</a>
+ *
+ * @licence Open Software License v3.0</a>
+ */
+
+public class MulgaraExternalTransactionFactory extends MulgaraTransactionFactory {
+ private Map<DatabaseSession, MulgaraExternalTransaction> associatedTransaction;
+ private Assoc1toNMap<DatabaseSession, MulgaraExternalTransaction> sessionXAMap;
+
+ private Map<DatabaseSession, MulgaraXAResourceContext> xaResources;
+
+ public MulgaraExternalTransactionFactory(MulgaraTransactionManager manager) {
+ super(manager);
+
+ this.associatedTransaction = new HashMap<DatabaseSession, MulgaraExternalTransaction>();
+ this.sessionXAMap = new Assoc1toNMap<DatabaseSession, MulgaraExternalTransaction>();
+ this.xaResources = new HashMap<DatabaseSession, MulgaraXAResourceContext>();
+ }
+
+ public MulgaraTransaction getTransaction(final DatabaseSession session, boolean write)
+ throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ MulgaraExternalTransaction xa = associatedTransaction.get(session);
+ if (xa == null) {
+ throw new MulgaraTransactionException("No externally mediated transaction associated with session");
+ } else if (write && xa != writeTransaction) {
+ throw new MulgaraTransactionException("RO-transaction associated with session when requesting write operation");
+ }
+
+ return xa;
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ protected MulgaraExternalTransaction createTransaction(final DatabaseSession session, Xid xid, boolean write)
+ throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ if (associatedTransaction.get(session) != null) {
+ throw new MulgaraTransactionException(
+ "Attempt to initiate transaction with existing transaction active with session");
+ }
+ if (write && manager.isHoldingWriteLock(session)) {
+ throw new MulgaraTransactionException("Attempt to initiate two write transactions from the same session");
+ }
+
+ if (write) {
+ runWithoutMutex(new TransactionOperation() {
+ public void execute() throws MulgaraTransactionException {
+ manager.obtainWriteLock(session);
+ }
+ });
+ try {
+ MulgaraExternalTransaction xa = new MulgaraExternalTransaction(this, xid, session.newOperationContext(true));
+ writeTransaction = xa;
+ associatedTransaction.put(session, xa);
+ sessionXAMap.put(session, xa);
+
+ return xa;
+ } catch (Throwable th) {
+ manager.releaseWriteLock(session);
+ throw new MulgaraTransactionException("Error initiating write transaction", th);
+ }
+ } else {
+ try {
+ MulgaraExternalTransaction xa = new MulgaraExternalTransaction(this, xid, session.newOperationContext(false));
+ associatedTransaction.put(session, xa);
+ sessionXAMap.put(session, xa);
+
+ return xa;
+ } catch (QueryException eq) {
+ throw new MulgaraTransactionException("Error obtaining new read-only operation-context", eq);
+ }
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public Set<MulgaraExternalTransaction> getTransactionsForSession(DatabaseSession session) {
+ acquireMutex();
+ try {
+ Set<MulgaraExternalTransaction> xas = sessionXAMap.getN(session);
+ return xas != null ? xas : Collections.<MulgaraExternalTransaction>emptySet();
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public XAResource getXAResource(DatabaseSession session, boolean writing) {
+ acquireMutex();
+ try {
+ MulgaraXAResourceContext xarc = xaResources.get(session);
+ if (xarc == null) {
+ xarc = new MulgaraXAResourceContext(this, session);
+ xaResources.put(session, xarc);
+ }
+
+ return xarc.getResource(writing);
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public void closingSession(DatabaseSession session) throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ try {
+ super.closingSession(session);
+ } finally {
+ xaResources.remove(session);
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public void transactionComplete(MulgaraExternalTransaction xa)
+ throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ if (xa == null) {
+ throw new IllegalArgumentException("Null transaction indicated completion");
+ }
+ DatabaseSession session = sessionXAMap.get1(xa);
+ if (xa == writeTransaction) {
+ manager.releaseWriteLock(session);
+ writeTransaction = null;
+ }
+ sessionXAMap.removeN(xa);
+ if (associatedTransaction.get(session) == xa) {
+ associatedTransaction.remove(session);
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public boolean hasAssociatedTransaction(DatabaseSession session) {
+ acquireMutex();
+ try {
+ return associatedTransaction.get(session) != null;
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public boolean associateTransaction(DatabaseSession session, MulgaraExternalTransaction xa) {
+ acquireMutex();
+ try {
+ if (associatedTransaction.get(session) != null) {
+ return false;
+ } else {
+ associatedTransaction.put(session, xa);
+ return true;
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public MulgaraExternalTransaction getAssociatedTransaction(DatabaseSession session) {
+ acquireMutex();
+ try {
+ return associatedTransaction.get(session);
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public void disassociateTransaction(DatabaseSession session, MulgaraExternalTransaction xa)
+ throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ if (associatedTransaction.get(session) == xa) {
+ associatedTransaction.remove(session);
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ void abortWriteTransaction() throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ if (writeTransaction != null) {
+ writeTransaction.abortTransaction("Explicit abort requested by write-lock manager", new Throwable());
+ writeTransaction = null;
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+}
Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransaction.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransaction.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransaction.java (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransaction.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,744 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com). Portions created
+ * by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd.
+ * All Rights Reserved.
+ *
+ * Work deriving from MulgaraTransaction Copyright (c) 2007 Topaz Foundation
+ * under contract by Andrae Muys (mailto:andrae at netymon.com).
+ */
+package org.mulgara.resolver;
+
+// Java 2 enterprise packages
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.xa.XAResource;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+// Local packages
+import org.mulgara.resolver.spi.DatabaseMetadata;
+import org.mulgara.resolver.spi.EnlistableResource;
+import org.mulgara.resolver.spi.ResolverSessionFactory;
+
+import org.mulgara.query.MulgaraTransactionException;
+import org.mulgara.query.TuplesException;
+import org.mulgara.query.QueryException;
+
+/**
+ * Responsible for the javax.transaction.Transaction object.
+ * Responsibilities
+ * Ensuring every begin or resume is followed by either a suspend or an end.
+ * Ensuring every suspend or end is preceeded by either a begin or a resume.
+ * In conjunction with TransactionalAnswer ensuring that
+ * all calls to operations on SubqueryAnswer are preceeded by a successful resume.
+ * all calls to operations on SubqueryAnswer conclude with a suspend as the last call prior to returning to the user.
+ * Collaborates with DatabaseTransactionManager to determine when to end the transaction.
+ *
+ * @created 2006-10-06
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <a href="mailto:mail at netymon.com">Netymon Pty Ltd</a>
+ *
+ * @copyright ©2006 <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
+ *
+ * @licence Open Software License v3.0
+ */
+public class MulgaraInternalTransaction implements MulgaraTransaction {
+ /**
+ * This is the state machine switch matching these states.
+ switch (state) {
+ case CONSTRUCTEDREF:
+ case CONSTRUCTEDUNREF:
+ case ACTUNREF:
+ case ACTREF:
+ case DEACTREF:
+ case FINISHED:
+ case FAILED:
+ }
+ */
+ private enum State { CONSTRUCTEDREF, CONSTRUCTEDUNREF, ACTUNREF, ACTREF, DEACTREF, FINISHED, FAILED };
+
+ /** Logger. */
+ private static final Logger logger =
+ Logger.getLogger(MulgaraInternalTransaction.class.getName());
+
+ private MulgaraInternalTransactionFactory factory;
+ private DatabaseOperationContext context;
+ private Set<EnlistableResource> enlisted;
+
+ private Transaction transaction;
+ private Thread currentThread;
+
+ private ReentrantLock activationMutex;
+
+ private State state;
+ private int inuse;
+ private int using;
+
+ private Throwable rollbackCause;
+
+ public MulgaraInternalTransaction(MulgaraInternalTransactionFactory factory, DatabaseOperationContext context)
+ throws IllegalArgumentException {
+ report("Creating Transaction");
+
+ try {
+ if (factory == null) {
+ throw new IllegalArgumentException("Manager null in MulgaraTransaction");
+ } else if (context == null) {
+ throw new IllegalArgumentException("OperationContext null in MulgaraTransaction");
+ }
+ this.factory = factory;
+ this.context = context;
+ this.enlisted = new HashSet<EnlistableResource>();
+ this.activationMutex = new ReentrantLock();
+ this.currentThread = null;
+
+ inuse = 0;
+ using = 0;
+ state = State.CONSTRUCTEDUNREF;
+ rollbackCause = null;
+ } finally {
+ report("Finished Creating Transaction");
+ }
+ }
+
+
+ void activate() throws MulgaraTransactionException {
+// report("Activating Transaction");
+ try {
+ synchronized (this) {
+ if (currentThread != null && !currentThread.equals(Thread.currentThread())) {
+ throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+ }
+ }
+
+ acquireActivationMutex();
+ try {
+ switch (state) {
+ case CONSTRUCTEDUNREF:
+ startTransaction();
+ inuse = 1;
+ state = State.ACTUNREF;
+ try {
+ context.initiate(this);
+ } catch (Throwable th) {
+ throw implicitRollback(th);
+ }
+ break;
+ case CONSTRUCTEDREF:
+ startTransaction();
+ inuse = 1;
+ using = 1;
+ state = State.ACTREF;
+ try {
+ context.initiate(this);
+ } catch (Throwable th) {
+ throw implicitRollback(th);
+ }
+ break;
+ case DEACTREF:
+ resumeTransaction();
+ inuse = 1;
+ state = State.ACTREF;
+ break;
+ case ACTREF:
+ case ACTUNREF:
+ inuse++;
+ break;
+ case FINISHED:
+ throw new MulgaraTransactionException("Attempt to activate terminated transaction");
+ case FAILED:
+ throw new MulgaraTransactionException("Attempt to activate failed transaction", rollbackCause);
+ }
+ } finally {
+ releaseActivationMutex();
+ }
+
+ try {
+ checkActivated();
+ } catch (MulgaraTransactionException em) {
+ throw abortTransaction("Activate failed post-condition check", em);
+ }
+ } catch (MulgaraTransactionException em) {
+ throw em;
+ } catch (Throwable th) {
+ throw abortTransaction("Error activating transaction", th);
+ } finally {
+// report("Leaving Activate transaction");
+ }
+ }
+
+
+ private void deactivate() throws MulgaraTransactionException {
+// report("Deactivating transaction");
+
+ try {
+ synchronized (this) {
+ if (currentThread == null) {
+ throw new MulgaraTransactionException("Transaction not associated with thread");
+ } else if (!currentThread.equals(Thread.currentThread())) {
+ throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+ }
+ }
+
+ acquireActivationMutex();
+ try {
+ switch (state) {
+ case ACTUNREF:
+ if (inuse == 1) {
+ commitTransaction();
+ }
+ inuse--;
+ break;
+ case ACTREF:
+ if (inuse == 1) {
+ suspendTransaction();
+ }
+ inuse--;
+ break;
+ case CONSTRUCTEDREF:
+ throw new MulgaraTransactionException("Attempt to deactivate uninitiated refed transaction");
+ case CONSTRUCTEDUNREF:
+ throw new MulgaraTransactionException("Attempt to deactivate uninitiated transaction");
+ case DEACTREF:
+ throw new IllegalStateException("Attempt to deactivate unactivated transaction");
+ case FINISHED:
+ if (inuse < 0) {
+ errorReport("Activation count failure - too many deacts - in finished transaction", null);
+ } else {
+ inuse--;
+ }
+ break;
+ case FAILED:
+ // Nothing to do here.
+ break;
+ }
+ } finally {
+ releaseActivationMutex();
+ }
+ } catch (MulgaraTransactionException em) {
+ throw em;
+ } catch (Throwable th) {
+ throw abortTransaction("Error deactivating transaction", th);
+ } finally {
+// report("Leaving Deactivate Transaction");
+ }
+ }
+
+ // Note: The transaction is often not activated when these are called.
+ // This occurs when setting autocommit, as this creates and
+ // references a transaction object that won't be started/activated
+ // until it is first used.
+ public void reference() throws MulgaraTransactionException {
+ try {
+ report("Referencing Transaction");
+
+ synchronized (this) {
+ if (currentThread != null && !currentThread.equals(Thread.currentThread())) {
+ throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+ }
+ }
+
+ switch (state) {
+ case CONSTRUCTEDUNREF:
+ state = State.CONSTRUCTEDREF;
+ break;
+ case ACTREF:
+ case ACTUNREF:
+ using++;
+ state = State.ACTREF;
+ break;
+ case DEACTREF:
+ using++;
+ break;
+ case CONSTRUCTEDREF:
+ throw new MulgaraTransactionException("Attempt to reference uninitated transaction twice");
+ case FINISHED:
+ throw new MulgaraTransactionException("Attempt to reference terminated transaction");
+ case FAILED:
+ throw new MulgaraTransactionException("Attempt to reference failed transaction", rollbackCause);
+ }
+ } catch (MulgaraTransactionException em) {
+ throw em;
+ } catch (Throwable th) {
+ report("Error referencing transaction");
+ throw implicitRollback(th);
+ } finally {
+ report("Leaving Reference Transaction");
+ }
+ }
+
+ public void dereference() throws MulgaraTransactionException {
+ report("Dereferencing Transaction");
+ try {
+ synchronized (this) {
+ if (currentThread != null && !currentThread.equals(Thread.currentThread())) {
+ throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+ }
+ }
+
+ switch (state) {
+ case ACTREF:
+ if (using == 1) {
+ state = State.ACTUNREF;
+ }
+ using--;
+ break;
+ case CONSTRUCTEDREF:
+ state = State.CONSTRUCTEDUNREF;
+ break;
+ case FINISHED:
+ case FAILED:
+ if (using < 1) {
+ errorReport("Reference count failure - too many derefs - in finished transaction", null);
+ } else {
+ using--;
+ }
+ break;
+ case ACTUNREF:
+ throw new IllegalStateException("Attempt to dereference unreferenced transaction");
+ case CONSTRUCTEDUNREF:
+ throw new MulgaraTransactionException("Attempt to dereference uninitated transaction");
+ case DEACTREF:
+ throw new IllegalStateException("Attempt to dereference deactivated transaction");
+ }
+ } catch (MulgaraTransactionException em) {
+ throw em;
+ } catch (Throwable th) {
+ throw implicitRollback(th);
+ } finally {
+ report("Dereferenced Transaction");
+ }
+ }
+
+ private void startTransaction() throws MulgaraTransactionException {
+ report("Initiating transaction");
+ try {
+ transaction = factory.transactionStart(this);
+ synchronized (this) {
+ currentThread = Thread.currentThread();
+ }
+ } catch (Throwable th) {
+ throw abortTransaction("Failed to start transaction", th);
+ }
+ }
+
+ private void resumeTransaction() throws MulgaraTransactionException {
+// report("Resuming transaction");
+ try {
+ factory.transactionResumed(this, transaction);
+ synchronized (this) {
+ currentThread = Thread.currentThread();
+ }
+ } catch (Throwable th) {
+ abortTransaction("Failed to resume transaction", th);
+ }
+ }
+
+ private void suspendTransaction() throws MulgaraTransactionException {
+// report("Suspending Transaction");
+ try {
+ if (using < 1) {
+ throw implicitRollback(
+ new MulgaraTransactionException("Attempt to suspend unreferenced transaction"));
+ }
+ transaction = factory.transactionSuspended(this);
+ synchronized (this) {
+ currentThread = null;
+ }
+ state = State.DEACTREF;
+ } catch (Throwable th) {
+ throw implicitRollback(th);
+ } finally {
+// report("Finished suspending transaction");
+ }
+ }
+
+ public void commitTransaction() throws MulgaraTransactionException {
+ report("Committing Transaction");
+ try {
+ transaction.commit();
+ } catch (Throwable th) {
+ throw implicitRollback(th);
+ }
+ try {
+ try {
+ transaction = null;
+ } finally { try {
+ state = State.FINISHED;
+ } finally { try {
+ context.clear();
+ } finally { try {
+ enlisted.clear();
+ } finally { try {
+ factory.transactionComplete(this);
+ } finally { try {
+ factory = null;
+ } finally {
+ report("Committed transaction");
+ } } } } } }
+ } catch (Throwable th) {
+ errorReport("Error cleaning up transaction post-commit", th);
+ throw new MulgaraTransactionException("Error cleaning up transaction post-commit", th);
+ }
+ }
+
+ public void heuristicRollback(String cause) throws MulgaraTransactionException {
+ implicitRollback(new MulgaraTransactionException(cause));
+ }
+
+ /**
+ * Rollback the transaction.
+ * We don't throw an exception here when transaction fails - this is expected,
+ * after all we requested it.
+ */
+ public void explicitRollback() throws MulgaraTransactionException {
+ synchronized (this) {
+ if (currentThread == null) {
+ throw new MulgaraTransactionException("Transaction failed activation check");
+ } else if (!currentThread.equals(Thread.currentThread())) {
+ throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+ }
+ }
+
+ try {
+ switch (state) {
+ case ACTUNREF:
+ case ACTREF:
+ transaction.rollback();
+ context.clear();
+ enlisted.clear();
+ factory.transactionComplete(this);
+ state = State.FINISHED;
+ break;
+ case DEACTREF:
+ throw new IllegalStateException("Attempt to rollback unactivated transaction");
+ case CONSTRUCTEDREF:
+ throw new MulgaraTransactionException("Attempt to rollback uninitiated ref'd transaction");
+ case CONSTRUCTEDUNREF:
+ throw new MulgaraTransactionException("Attempt to rollback uninitiated unref'd transaction");
+ case FINISHED:
+ throw new MulgaraTransactionException("Attempt to rollback finished transaction");
+ case FAILED:
+ throw new MulgaraTransactionException("Attempt to rollback failed transaction");
+ }
+ } catch (MulgaraTransactionException em) {
+ throw em;
+ } catch (Throwable th) {
+ throw implicitRollback(th);
+ }
+ }
+
+ /**
+ * This will endevour to terminate the transaction via a rollback - if this
+ * fails it will abort the transaction.
+ * If the rollback succeeds then this method will return a suitable
+ * MulgaraTransactionException to be thrown by the caller.
+ * If the rollback fails then this method will throw the resulting exception
+ * from abortTransaction().
+ * Post-condition: The transaction is terminated and cleaned up.
+ */
+ MulgaraTransactionException implicitRollback(Throwable cause) throws MulgaraTransactionException {
+ try {
+ report("Implicit Rollback triggered");
+
+ synchronized (this) {
+ if (currentThread == null) {
+ throw new MulgaraTransactionException("Transaction not associated with thread");
+ } else if (!currentThread.equals(Thread.currentThread())) {
+ throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+ }
+ }
+
+ if (rollbackCause != null) {
+ errorReport("Cascading error, transaction already rolled back", cause);
+ errorReport("Cascade error, expected initial cause", rollbackCause);
+
+ return new MulgaraTransactionException("Transaction already in rollback", cause);
+ }
+
+ switch (state) {
+ case ACTUNREF:
+ case ACTREF:
+ rollbackCause = cause;
+ transaction.rollback();
+ transaction = null;
+ context.clear();
+ enlisted.clear();
+ state = State.FAILED;
+ factory.transactionComplete(this);
+ factory = null;
+ return new MulgaraTransactionException("Transaction rollback triggered", cause);
+ case DEACTREF:
+ throw new IllegalStateException("Attempt to rollback deactivated transaction");
+ case CONSTRUCTEDREF:
+ throw new MulgaraTransactionException("Attempt to rollback uninitiated ref'd transaction");
+ case CONSTRUCTEDUNREF:
+ throw new MulgaraTransactionException("Attempt to rollback uninitiated unref'd transaction");
+ case FINISHED:
+ throw new MulgaraTransactionException("Attempt to rollback finished transaction");
+ case FAILED:
+ throw new MulgaraTransactionException("Attempt to rollback failed transaction");
+ default:
+ throw new MulgaraTransactionException("Unknown state");
+ }
+ } catch (Throwable th) {
+ try {
+ errorReport("Attempt to rollback failed; initiating cause: ", cause);
+ } finally {
+ throw abortTransaction("Failed to rollback normally - see log for inititing cause", th);
+ }
+ } finally {
+ report("Leaving implicitRollback");
+ }
+ }
+
+ /**
+ * Forces the transaction to be abandoned, including bypassing JTA to directly
+ * rollback/abort the underlying store-phases if required.
+ * Heavilly nested try{}finally{} should guarentee that even JVM errors should
+ * not prevent this function from cleaning up anything that can be cleaned up.
+ * We have to delegate to the OperationContext the abort() on the resolvers as
+ * only it has full knowledge of which resolvers are associated with this
+ * transaction.
+ */
+ public MulgaraTransactionException abortTransaction(String errorMessage, Throwable cause)
+ throws MulgaraTransactionException {
+ // We need to notify the factory here - this is serious, we
+ // need to rollback this transaction, but if we have reached here
+ // we have failed to obtain a valid transaction to rollback!
+ try {
+ try {
+ errorReport(errorMessage + " - Aborting", cause);
+ } finally { try {
+ if (transaction != null) {
+ transaction.rollback();
+ }
+ } finally { try {
+ factory.transactionAborted(this);
+ } finally { try {
+ abortEnlistedResources();
+ } finally { try {
+ context.clear();
+ } finally { try {
+ enlisted.clear();
+ } finally { try {
+ transaction = null;
+ } finally { try {
+ factory = null;
+ } finally {
+ state = State.FAILED;
+ } } } } } } } }
+ return new MulgaraTransactionException(errorMessage + " - Aborting", cause);
+ } catch (Throwable th) {
+ throw new MulgaraTransactionException(errorMessage + " - Failed to abort cleanly", th);
+ } finally {
+ report("Leaving abortTransaction");
+ }
+ }
+
+ /**
+ * Used to bypass JTA and explicitly abort resources behind the scenes.
+ */
+ private void abortEnlistedResources() {
+ for (EnlistableResource e : enlisted) {
+ try {
+ e.abort();
+ } catch (Throwable th) {
+ try {
+ errorReport("Error aborting enlistable resource", th);
+ } catch (Throwable ignore) { }
+ }
+ }
+ }
+
+ public void execute(Operation operation,
+ ResolverSessionFactory resolverSessionFactory, // FIXME: We shouldn't need this. - only used for backup and restore operations.
+ DatabaseMetadata metadata) throws MulgaraTransactionException {
+ report("Executing Operation");
+ try {
+ activate();
+ try {
+ operation.execute(context,
+ context.getSystemResolver(),
+ resolverSessionFactory,
+ metadata);
+ } catch (Throwable th) {
+ throw implicitRollback(th);
+ } finally {
+ deactivate();
+ }
+ } finally {
+ report("Executed Operation");
+ }
+ }
+
+ public AnswerOperationResult execute(AnswerOperation ao) throws TuplesException {
+ debugReport("Executing AnswerOperation");
+ try {
+ activate();
+ try {
+ ao.execute();
+ return ao.getResult();
+ } catch (Throwable th) {
+ throw implicitRollback(th);
+ } finally {
+ deactivate();
+ }
+ } catch (MulgaraTransactionException em) {
+ throw new TuplesException("Transaction error", em);
+ } finally {
+ debugReport("Executed AnswerOperation");
+ }
+ }
+
+
+ public void execute(TransactionOperation to) throws MulgaraTransactionException {
+ report("Executing TransactionOperation");
+ try {
+ activate();
+ try {
+ to.execute();
+ } catch (Throwable th) {
+ throw implicitRollback(th);
+ } finally {
+ deactivate();
+ }
+ } finally {
+ report("Executed TransactionOperation");
+ }
+ }
+
+ public void enlist(EnlistableResource enlistable) throws MulgaraTransactionException {
+ try {
+ synchronized (this) {
+ if (currentThread == null) {
+ throw new MulgaraTransactionException("Transaction not associated with thread");
+ } else if (!currentThread.equals(Thread.currentThread())) {
+ throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+ }
+ }
+
+ if (enlisted.contains(enlistable)) {
+ return;
+ }
+
+ switch (state) {
+ case ACTUNREF:
+ case ACTREF:
+ transaction.enlistResource(enlistable.getXAResource());
+ enlisted.add(enlistable);
+ break;
+ case CONSTRUCTEDREF:
+ throw new MulgaraTransactionException("Attempt to enlist resource in uninitated ref'd transaction");
+ case CONSTRUCTEDUNREF:
+ throw new MulgaraTransactionException("Attempt to enlist resource in uninitated unref'd transaction");
+ case DEACTREF:
+ throw new MulgaraTransactionException("Attempt to enlist resource in unactivated transaction");
+ case FINISHED:
+ throw new MulgaraTransactionException("Attempt to enlist resource in finished transaction");
+ case FAILED:
+ throw new MulgaraTransactionException("Attempt to enlist resource in failed transaction");
+ }
+ } catch (Throwable th) {
+ throw implicitRollback(th);
+ }
+ }
+
+ //
+ // Used internally
+ //
+
+ private void checkActivated() throws MulgaraTransactionException {
+ synchronized (this) {
+ if (currentThread == null) {
+ throw new MulgaraTransactionException("Transaction not associated with thread");
+ } else if (!currentThread.equals(Thread.currentThread())) {
+ throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
+ }
+ }
+
+ switch (state) {
+ case ACTUNREF:
+ case ACTREF:
+ if (inuse < 0 || using < 0) {
+ throw new MulgaraTransactionException("Reference Failure, using: " + using + ", inuse: " + inuse);
+ }
+ return;
+ case CONSTRUCTEDREF:
+ throw new MulgaraTransactionException("Transaction (ref) uninitiated");
+ case CONSTRUCTEDUNREF:
+ throw new MulgaraTransactionException("Transaction (unref) uninitiated");
+ case DEACTREF:
+ throw new MulgaraTransactionException("Transaction deactivated");
+ case FINISHED:
+ throw new MulgaraTransactionException("Transaction is terminated");
+ case FAILED:
+ throw new MulgaraTransactionException("Transaction is failed", rollbackCause);
+ }
+ }
+
+ private void acquireActivationMutex() {
+ activationMutex.lock();
+ }
+
+ private void releaseActivationMutex() {
+ activationMutex.unlock();
+ }
+
+ protected void finalize() {
+ report("GC-finalize");
+ if (state != State.FINISHED && state != State.FAILED) {
+ errorReport("Finalizing incomplete transaction - aborting...", null);
+ try {
+ abortTransaction("Transaction finalized while still valid", new Throwable());
+ } catch (Throwable th) {
+ errorReport("Attempt to abort transaction from finalize failed", th);
+ }
+ }
+
+ if (state != State.FAILED && (inuse != 0 || using != 0)) {
+ errorReport("Reference counting error in transaction", null);
+ }
+
+ if (factory != null || transaction != null) {
+ errorReport("Transaction not terminated properly", null);
+ }
+ }
+
+ private void report(String desc) {
+ if (logger.isInfoEnabled()) {
+ logger.info(desc + ": " + System.identityHashCode(this) + ", state=" + state +
+ ", inuse=" + inuse + ", using=" + using);
+ }
+ }
+
+ private void debugReport(String desc) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(desc + ": " + System.identityHashCode(this) + ", state=" + state +
+ ", inuse=" + inuse + ", using=" + using);
+ }
+ }
+
+ private void errorReport(String desc, Throwable cause) {
+ logger.error(desc + ": " + System.identityHashCode(this) + ", state=" + state +
+ ", inuse=" + inuse + ", using=" + using, cause != null ? cause : new Throwable());
+ }
+}
Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransactionFactory.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransactionFactory.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransactionFactory.java (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraInternalTransactionFactory.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,421 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com). Portions created
+ * by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd.
+ * All Rights Reserved.
+ *
+ * Work deriving from MulgaraTransactionManager Copyright (c) 2007 Topaz
+ * Foundation under contract by Andrae Muys (mailto:andrae at netymon.com).
+ */
+
+package org.mulgara.resolver;
+
+// Java2 packages
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+// Local packages
+import org.mulgara.query.MulgaraTransactionException;
+import org.mulgara.transaction.TransactionManagerFactory;
+import org.mulgara.util.Assoc1toNMap;
+
+/**
+ * Implements the internal transaction controls offered by Session.
+ *
+ * @created 2006-10-06
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ *
+ * @copyright ©2006 <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
+ *
+ * @licence Open Software License v3.0</a>
+ */
+
+public class MulgaraInternalTransactionFactory extends MulgaraTransactionFactory {
+ /** Logger. */
+ private static final Logger logger =
+ Logger.getLogger(MulgaraInternalTransactionFactory.class.getName());
+
+ private boolean autoCommit;
+
+ /** Set of sessions whose transactions have been rolledback.*/
+ private Set<DatabaseSession> failedSessions;
+
+ /** Map of threads to active transactions. */
+ private Map<Thread, MulgaraTransaction> activeTransactions;
+
+ private Assoc1toNMap<DatabaseSession, MulgaraTransaction> sessionXAMap;
+
+ private final TransactionManager transactionManager;
+
+ public MulgaraInternalTransactionFactory(MulgaraTransactionManager manager, TransactionManagerFactory transactionManagerFactory) {
+ super(manager);
+ this.autoCommit = true;
+
+ this.failedSessions = new HashSet<DatabaseSession>();
+ this.activeTransactions = new HashMap<Thread, MulgaraTransaction>();
+ this.sessionXAMap = new Assoc1toNMap<DatabaseSession, MulgaraTransaction>();
+
+ this.transactionManager = transactionManagerFactory.newTransactionManager();
+ }
+
+ public MulgaraTransaction getTransaction(final DatabaseSession session, boolean write)
+ throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ if (manager.isHoldingWriteLock(session)) {
+ return writeTransaction;
+ }
+
+ try {
+ MulgaraInternalTransaction transaction;
+ if (write) {
+ runWithoutMutex(new TransactionOperation() {
+ public void execute() throws MulgaraTransactionException {
+ manager.obtainWriteLock(session);
+ }
+ });
+ try {
+ assert writeTransaction == null;
+ writeTransaction = transaction =
+ new MulgaraInternalTransaction(this, session.newOperationContext(true));
+ } catch (Throwable th) {
+ manager.releaseWriteLock(session);
+ throw new MulgaraTransactionException("Error creating write transaction", th);
+ }
+ } else {
+ transaction = new MulgaraInternalTransaction(this, session.newOperationContext(false));
+ }
+
+ sessionXAMap.put(session, transaction);
+
+ return transaction;
+ } catch (MulgaraTransactionException em) {
+ throw em;
+ } catch (Exception e) {
+ throw new MulgaraTransactionException("Error creating transaction", e);
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public Set<MulgaraTransaction> getTransactionsForSession(DatabaseSession session) {
+ acquireMutex();
+ try {
+ Set <MulgaraTransaction> xas = sessionXAMap.getN(session);
+ return xas == null ? Collections.<MulgaraTransaction>emptySet() : xas;
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public MulgaraTransaction newMulgaraTransaction(DatabaseOperationContext context)
+ throws MulgaraTransactionException {
+ return new MulgaraInternalTransaction(this, context);
+ }
+
+
+ public void commit(DatabaseSession session) throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ manager.reserveWriteLock(session);
+ try {
+ if (failedSessions.contains(session)) {
+ throw new MulgaraTransactionException("Attempting to commit failed exception");
+ } else if (!manager.isHoldingWriteLock(session)) {
+ throw new MulgaraTransactionException(
+ "Attempting to commit while not the current writing transaction");
+ }
+
+ setAutoCommit(session, true);
+ setAutoCommit(session, false);
+ } finally {
+ manager.releaseReserve(session);
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+
+ /**
+ * This is an explicit, user-specified rollback.
+ *
+ * This needs to be distinguished from an implicit rollback triggered by failure.
+ */
+ public void rollback(DatabaseSession session) throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ manager.reserveWriteLock(session);
+ try {
+ if (manager.isHoldingWriteLock(session)) {
+ try {
+ writeTransaction.execute(new TransactionOperation() {
+ public void execute() throws MulgaraTransactionException {
+ writeTransaction.heuristicRollback("Explicit Rollback");
+ }
+ });
+ // FIXME: Should be checking status here, not writelock.
+ if (manager.isHoldingWriteLock(session)) {
+ // transaction referenced by something - need to explicitly end it.
+ writeTransaction.abortTransaction("Rollback failed",
+ new MulgaraTransactionException("Rollback failed to terminate write transaction"));
+ }
+ } finally {
+ failedSessions.add(session);
+ setAutoCommit(session, false);
+ }
+ } else if (failedSessions.contains(session)) {
+ failedSessions.remove(session);
+ setAutoCommit(session, false);
+ } else {
+ throw new MulgaraTransactionException(
+ "Attempt to rollback while not in the current writing transaction");
+ }
+ } finally {
+ manager.releaseReserve(session);
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public void setAutoCommit(DatabaseSession session, boolean autoCommit)
+ throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ if (manager.isHoldingWriteLock(session) && failedSessions.contains(session)) {
+ writeTransaction.abortTransaction("Session failed and still holding writeLock",
+ new MulgaraTransactionException("Failed Session in setAutoCommit"));
+ }
+
+ if (manager.isHoldingWriteLock(session) || failedSessions.contains(session)) {
+ if (autoCommit) {
+ // AutoCommit off -> on === branch on current state of transaction.
+ if (manager.isHoldingWriteLock(session)) {
+ // Within active transaction - commit and finalise.
+ try {
+ runWithoutMutex(new TransactionOperation() {
+ public void execute() throws MulgaraTransactionException {
+ writeTransaction.execute(new TransactionOperation() {
+ public void execute() throws MulgaraTransactionException {
+ writeTransaction.dereference();
+ ((MulgaraInternalTransaction)writeTransaction).commitTransaction();
+ }
+ });
+ }
+ });
+ } finally {
+ // This should have been cleaned up by the commit above, but if it
+ // hasn't then if we don't release here we could deadlock the
+ // transaction manager
+ if (manager.isHoldingWriteLock(session)) {
+ manager.releaseWriteLock(session);
+ }
+ this.autoCommit = true;
+ }
+ } else if (failedSessions.contains(session)) {
+ // Within failed transaction - cleanup.
+ failedSessions.remove(session);
+ }
+ } else {
+ if (!manager.isHoldingWriteLock(session)) {
+ if (failedSessions.contains(session)) {
+ failedSessions.remove(session);
+ setAutoCommit(session, false);
+ } else {
+ throw new IllegalStateException("Can't reach here");
+ }
+ } else {
+ // AutoCommit off -> off === no-op. Log info.
+ if (logger.isInfoEnabled()) {
+ logger.info("Attempt to set autocommit false twice", new Throwable());
+ }
+ }
+ }
+ } else {
+ if (autoCommit) {
+ // AutoCommit on -> on === no-op. Log info.
+ logger.info("Attempting to set autocommit true without setting it false");
+ } else {
+ // AutoCommit on -> off == Start new transaction.
+ getTransaction(session, true); // Set's writeTransaction.
+ writeTransaction.reference();
+ this.autoCommit = false;
+ }
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ //
+ // Transaction livecycle callbacks.
+ //
+
+ public Transaction transactionStart(MulgaraTransaction transaction) throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ try {
+ logger.info("Beginning Transaction");
+ if (activeTransactions.get(Thread.currentThread()) != null) {
+ throw new MulgaraTransactionException(
+ "Attempt to start transaction in thread with exiting active transaction.");
+ } else if (activeTransactions.containsValue(transaction)) {
+ throw new MulgaraTransactionException("Attempt to start transaction twice");
+ }
+
+ transactionManager.begin();
+ Transaction jtaTrans = transactionManager.getTransaction();
+
+ activeTransactions.put(Thread.currentThread(), transaction);
+
+ return jtaTrans;
+ } catch (Exception e) {
+ throw new MulgaraTransactionException("Transaction Begin Failed", e);
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public void transactionResumed(MulgaraTransaction transaction, Transaction jtaXA)
+ throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ if (activeTransactions.get(Thread.currentThread()) != null) {
+ throw new MulgaraTransactionException(
+ "Attempt to resume transaction in already activated thread");
+ } else if (activeTransactions.containsValue(transaction)) {
+ throw new MulgaraTransactionException("Attempt to resume active transaction");
+ }
+
+ try {
+ transactionManager.resume(jtaXA);
+ activeTransactions.put(Thread.currentThread(), transaction);
+ } catch (Exception e) {
+ throw new MulgaraTransactionException("Resume Failed", e);
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public Transaction transactionSuspended(MulgaraTransaction transaction)
+ throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ try {
+ if (transaction != activeTransactions.get(Thread.currentThread())) {
+ throw new MulgaraTransactionException(
+ "Attempt to suspend transaction from outside thread");
+ }
+
+ if (autoCommit && transaction == writeTransaction) {
+ logger.error("Attempt to suspend write transaction without setting AutoCommit Off");
+ throw new MulgaraTransactionException(
+ "Attempt to suspend write transaction without setting AutoCommit Off");
+ }
+
+ Transaction xa = transactionManager.suspend();
+ activeTransactions.remove(Thread.currentThread());
+
+ return xa;
+ } catch (Throwable th) {
+ logger.error("Attempt to suspend failed", th);
+ try {
+ transactionManager.setRollbackOnly();
+ } catch (Throwable t) {
+ logger.error("Attempt to setRollbackOnly() failed", t);
+ }
+ throw new MulgaraTransactionException("Suspend failed", th);
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public void transactionComplete(MulgaraTransaction transaction) throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ logger.debug("Transaction Complete");
+ DatabaseSession session = sessionXAMap.get1(transaction);
+ if (session == null) {
+ throw new MulgaraTransactionException("No associated session found for transaction");
+ }
+ if (manager.isHoldingWriteLock(session)) {
+ manager.releaseWriteLock(session);
+ writeTransaction = null;
+ }
+
+ sessionXAMap.removeN(transaction);
+ activeTransactions.remove(Thread.currentThread());
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public void transactionAborted(MulgaraTransaction transaction) {
+ acquireMutex();
+ try {
+ try {
+ // Make sure this cleans up the transaction metadata - this transaction is DEAD!
+ if (transaction == writeTransaction) {
+ failedSessions.add(sessionXAMap.get1(transaction));
+ }
+ transactionComplete(transaction);
+ } catch (Throwable th) {
+ // FIXME: This should probably abort the entire server after logging the error!
+ logger.error("Error managing transaction abort", th);
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public void setTransactionTimeout(int transactionTimeout) {
+ try {
+ transactionManager.setTransactionTimeout(transactionTimeout);
+ } catch (SystemException es) {
+ logger.warn("Unable to set transaction timeout: " + transactionTimeout, es);
+ }
+ }
+
+ void abortWriteTransaction() throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ if (writeTransaction != null) {
+ writeTransaction.abortTransaction("Explicit abort requested by write-lock manager", new Throwable());
+ writeTransaction = null;
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+}
Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransaction.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransaction.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransaction.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -11,16 +11,15 @@
*
* This file is an original work developed by Netymon Pty Ltd
* (http://www.netymon.com, mailto:mail at netymon.com). Portions created
- * by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd.
+ * by Netymon Pty Ltd are Copyright (c) 2007 Netymon Pty Ltd.
* All Rights Reserved.
+ *
+ * Migration to interface Copyright (c) 2007 Topaz Foundation
+ * under contract by Andrae Muys (mailto:andrae at netymon.com).
*/
package org.mulgara.resolver;
// Java 2 enterprise packages
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.locks.ReentrantLock;
-import javax.transaction.Transaction;
// Third party packages
import org.apache.log4j.Logger;
@@ -34,17 +33,8 @@
import org.mulgara.query.TuplesException;
/**
- * Responsible for the javax.transaction.Transaction object.
- * Responsibilities
- * Ensuring every begin or resume is followed by either a suspend or an end.
- * Ensuring every suspend or end is preceeded by either a begin or a resume.
- * In conjunction with TransactionalAnswer ensuring that
- * all calls to operations on SubqueryAnswer are preceeded by a successful resume.
- * all calls to operations on SubqueryAnswer conclude with a suspend as the last call prior to returning to the user.
- * Collaborates with DatabaseTransactionManager to determine when to end the transaction.
+ * @created 2007-11-06
*
- * @created 2006-10-06
- *
* @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
*
* @version $Revision: $
@@ -55,682 +45,52 @@
*
* @company <a href="mailto:mail at netymon.com">Netymon Pty Ltd</a>
*
- * @copyright ©2006 <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
+ * @copyright ©2007 <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
*
* @licence Open Software License v3.0
*/
-public class MulgaraTransaction {
+public interface MulgaraTransaction {
+ void reference() throws MulgaraTransactionException;
+ void dereference() throws MulgaraTransactionException;
+
/**
- * This is the state machine switch matching these states.
- switch (state) {
- case CONSTRUCTEDREF:
- case CONSTRUCTEDUNREF:
- case ACTUNREF:
- case ACTREF:
- case DEACTREF:
- case FINISHED:
- case FAILED:
- }
+ * Forces the transaction to be abandoned, including bypassing JTA to directly
+ * rollback/abort the underlying store-phases if required.
+ * This this transaction is externally managed this amounts to a heuristic
+ * rollback decision and should be treated as such.
+ *
+ * @return an exception constructed with the provided error-message and cause.
+ * @throws MulgaraTransactionException if a further error is encounted while
+ * attempting to abort.
*/
- private enum State { CONSTRUCTEDREF, CONSTRUCTEDUNREF, ACTUNREF, ACTREF, DEACTREF, FINISHED, FAILED };
+ MulgaraTransactionException abortTransaction(String errorMessage, Throwable cause) throws MulgaraTransactionException;
- /** Logger. */
- private static final Logger logger =
- Logger.getLogger(MulgaraTransaction.class.getName());
+ void heuristicRollback(String cause) throws MulgaraTransactionException;
- private MulgaraTransactionManager manager;
- private DatabaseOperationContext context;
- private Set<EnlistableResource> enlisted;
-
- private Transaction transaction;
- private Thread currentThread;
-
- private ReentrantLock activationMutex;
-
- private State state;
- private int inuse;
- private int using;
-
- private Throwable rollbackCause;
-
- public MulgaraTransaction(MulgaraTransactionManager manager, DatabaseOperationContext context)
- throws IllegalArgumentException {
- report("Creating Transaction");
-
- try {
- if (manager == null) {
- throw new IllegalArgumentException("Manager null in MulgaraTransaction");
- } else if (context == null) {
- throw new IllegalArgumentException("OperationContext null in MulgaraTransaction");
- }
- this.manager = manager;
- this.context = context;
- this.enlisted = new HashSet<EnlistableResource>();
- this.activationMutex = new ReentrantLock();
- this.currentThread = null;
-
- inuse = 0;
- using = 0;
- state = State.CONSTRUCTEDUNREF;
- rollbackCause = null;
- } finally {
- report("Finished Creating Transaction");
- }
- }
-
-
- void activate() throws MulgaraTransactionException {
-// report("Activating Transaction");
- try {
- synchronized (this) {
- if (currentThread != null && !currentThread.equals(Thread.currentThread())) {
- throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
- }
- }
-
- acquireActivationMutex();
- try {
- switch (state) {
- case CONSTRUCTEDUNREF:
- startTransaction();
- inuse = 1;
- state = State.ACTUNREF;
- try {
- context.initiate(this);
- } catch (Throwable th) {
- throw implicitRollback(th);
- }
- break;
- case CONSTRUCTEDREF:
- startTransaction();
- inuse = 1;
- using = 1;
- state = State.ACTREF;
- try {
- context.initiate(this);
- } catch (Throwable th) {
- throw implicitRollback(th);
- }
- break;
- case DEACTREF:
- resumeTransaction();
- inuse = 1;
- state = State.ACTREF;
- break;
- case ACTREF:
- case ACTUNREF:
- inuse++;
- break;
- case FINISHED:
- throw new MulgaraTransactionException("Attempt to activate terminated transaction");
- case FAILED:
- throw new MulgaraTransactionException("Attempt to activate failed transaction", rollbackCause);
- }
- } finally {
- releaseActivationMutex();
- }
-
- try {
- checkActivated();
- } catch (MulgaraTransactionException em) {
- throw abortTransaction("Activate failed post-condition check", em);
- }
- } catch (MulgaraTransactionException em) {
- throw em;
- } catch (Throwable th) {
- throw abortTransaction("Error activating transaction", th);
- } finally {
-// report("Leaving Activate transaction");
- }
- }
-
-
- private void deactivate() throws MulgaraTransactionException {
-// report("Deactivating transaction");
-
- try {
- synchronized (this) {
- if (currentThread == null) {
- throw new MulgaraTransactionException("Transaction not associated with thread");
- } else if (!currentThread.equals(Thread.currentThread())) {
- throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
- }
- }
-
- acquireActivationMutex();
- try {
- switch (state) {
- case ACTUNREF:
- if (inuse == 1) {
- commitTransaction();
- }
- inuse--;
- break;
- case ACTREF:
- if (inuse == 1) {
- suspendTransaction();
- }
- inuse--;
- break;
- case CONSTRUCTEDREF:
- throw new MulgaraTransactionException("Attempt to deactivate uninitiated refed transaction");
- case CONSTRUCTEDUNREF:
- throw new MulgaraTransactionException("Attempt to deactivate uninitiated transaction");
- case DEACTREF:
- throw new IllegalStateException("Attempt to deactivate unactivated transaction");
- case FINISHED:
- if (inuse < 0) {
- errorReport("Activation count failure - too many deacts - in finished transaction", null);
- } else {
- inuse--;
- }
- break;
- case FAILED:
- // Nothing to do here.
- break;
- }
- } finally {
- releaseActivationMutex();
- }
- } catch (MulgaraTransactionException em) {
- throw em;
- } catch (Throwable th) {
- throw abortTransaction("Error deactivating transaction", th);
- } finally {
-// report("Leaving Deactivate Transaction");
- }
- }
-
- // Note: The transaction is often not activated when these are called.
- // This occurs when setting autocommit, as this creates and
- // references a transaction object that won't be started/activated
- // until it is first used.
- void reference() throws MulgaraTransactionException {
- try {
- report("Referencing Transaction");
-
- synchronized (this) {
- if (currentThread != null && !currentThread.equals(Thread.currentThread())) {
- throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
- }
- }
-
- switch (state) {
- case CONSTRUCTEDUNREF:
- state = State.CONSTRUCTEDREF;
- break;
- case ACTREF:
- case ACTUNREF:
- using++;
- state = State.ACTREF;
- break;
- case DEACTREF:
- using++;
- break;
- case CONSTRUCTEDREF:
- throw new MulgaraTransactionException("Attempt to reference uninitated transaction twice");
- case FINISHED:
- throw new MulgaraTransactionException("Attempt to reference terminated transaction");
- case FAILED:
- throw new MulgaraTransactionException("Attempt to reference failed transaction", rollbackCause);
- }
- } catch (MulgaraTransactionException em) {
- throw em;
- } catch (Throwable th) {
- report("Error referencing transaction");
- throw implicitRollback(th);
- } finally {
- report("Leaving Reference Transaction");
- }
- }
-
- void dereference() throws MulgaraTransactionException {
- report("Dereferencing Transaction");
- try {
- synchronized (this) {
- if (currentThread != null && !currentThread.equals(Thread.currentThread())) {
- throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
- }
- }
-
- switch (state) {
- case ACTREF:
- if (using == 1) {
- state = State.ACTUNREF;
- }
- using--;
- break;
- case CONSTRUCTEDREF:
- state = State.CONSTRUCTEDUNREF;
- break;
- case FINISHED:
- case FAILED:
- if (using < 1) {
- errorReport("Reference count failure - too many derefs - in finished transaction", null);
- } else {
- using--;
- }
- break;
- case ACTUNREF:
- throw new IllegalStateException("Attempt to dereference unreferenced transaction");
- case CONSTRUCTEDUNREF:
- throw new MulgaraTransactionException("Attempt to dereference uninitated transaction");
- case DEACTREF:
- throw new IllegalStateException("Attempt to dereference deactivated transaction");
- }
- } catch (MulgaraTransactionException em) {
- throw em;
- } catch (Throwable th) {
- throw implicitRollback(th);
- } finally {
- report("Dereferenced Transaction");
- }
- }
-
- private void startTransaction() throws MulgaraTransactionException {
- report("Initiating transaction");
- try {
- transaction = manager.transactionStart(this);
- synchronized (this) {
- currentThread = Thread.currentThread();
- }
- } catch (Throwable th) {
- throw abortTransaction("Failed to start transaction", th);
- }
- }
-
- private void resumeTransaction() throws MulgaraTransactionException {
-// report("Resuming transaction");
- try {
- manager.transactionResumed(this, transaction);
- synchronized (this) {
- currentThread = Thread.currentThread();
- }
- } catch (Throwable th) {
- abortTransaction("Failed to resume transaction", th);
- }
- }
-
- private void suspendTransaction() throws MulgaraTransactionException {
-// report("Suspending Transaction");
- try {
- if (using < 1) {
- throw implicitRollback(
- new MulgaraTransactionException("Attempt to suspend unreferenced transaction"));
- }
- transaction = manager.transactionSuspended(this);
- synchronized (this) {
- currentThread = null;
- }
- state = State.DEACTREF;
- } catch (Throwable th) {
- throw implicitRollback(th);
- } finally {
-// report("Finished suspending transaction");
- }
- }
-
- public void commitTransaction() throws MulgaraTransactionException {
- report("Committing Transaction");
- try {
- transaction.commit();
- } catch (Throwable th) {
- throw implicitRollback(th);
- }
- try {
- try {
- transaction = null;
- } finally { try {
- state = State.FINISHED;
- } finally { try {
- context.clear();
- } finally { try {
- enlisted.clear();
- } finally { try {
- manager.transactionComplete(this);
- } finally { try {
- manager = null;
- } finally {
- report("Committed transaction");
- } } } } } }
- } catch (Throwable th) {
- errorReport("Error cleaning up transaction post-commit", th);
- throw new MulgaraTransactionException("Error cleaning up transaction post-commit", th);
- }
- }
-
/**
- * Rollback the transaction.
- * We don't throw an exception here when transaction fails - this is expected,
- * after all we requested it.
+ * Execute the specified operation.
+ *
+ * FIXME: We shouldn't need resolverSessionFactory as this is only used for backup and restore operations.
*/
- public void explicitRollback() throws MulgaraTransactionException {
- synchronized (this) {
- if (currentThread == null) {
- throw new MulgaraTransactionException("Transaction failed activation check");
- } else if (!currentThread.equals(Thread.currentThread())) {
- throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
- }
- }
+ void execute(Operation operation,
+ ResolverSessionFactory resolverSessionFactory,
+ DatabaseMetadata metadata) throws MulgaraTransactionException;
- try {
- switch (state) {
- case ACTUNREF:
- case ACTREF:
- transaction.rollback();
- context.clear();
- enlisted.clear();
- manager.transactionComplete(this);
- state = State.FINISHED;
- break;
- case DEACTREF:
- throw new IllegalStateException("Attempt to rollback unactivated transaction");
- case CONSTRUCTEDREF:
- throw new MulgaraTransactionException("Attempt to rollback uninitiated ref'd transaction");
- case CONSTRUCTEDUNREF:
- throw new MulgaraTransactionException("Attempt to rollback uninitiated unref'd transaction");
- case FINISHED:
- throw new MulgaraTransactionException("Attempt to rollback finished transaction");
- case FAILED:
- throw new MulgaraTransactionException("Attempt to rollback failed transaction");
- }
- } catch (MulgaraTransactionException em) {
- throw em;
- } catch (Throwable th) {
- throw implicitRollback(th);
- }
- }
-
/**
- * This will endevour to terminate the transaction via a rollback - if this
- * fails it will abort the transaction.
- * If the rollback succeeds then this method will return a suitable
- * MulgaraTransactionException to be thrown by the caller.
- * If the rollback fails then this method will throw the resulting exception
- * from abortTransaction().
- * Post-condition: The transaction is terminated and cleaned up.
+ * Execute the specified operation.
+ * Used by TransactionalAnswer to ensure transactional guarantees are met when
+ * using a result whose transaction may be in a suspended state.
*/
- MulgaraTransactionException implicitRollback(Throwable cause) throws MulgaraTransactionException {
- try {
- report("Implicit Rollback triggered");
+ AnswerOperationResult execute(AnswerOperation ao) throws TuplesException;
- synchronized (this) {
- if (currentThread == null) {
- throw new MulgaraTransactionException("Transaction not associated with thread");
- } else if (!currentThread.equals(Thread.currentThread())) {
- throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
- }
- }
-
- if (rollbackCause != null) {
- errorReport("Cascading error, transaction already rolled back", cause);
- errorReport("Cascade error, expected initial cause", rollbackCause);
-
- return new MulgaraTransactionException("Transaction already in rollback", cause);
- }
-
- switch (state) {
- case ACTUNREF:
- case ACTREF:
- rollbackCause = cause;
- transaction.rollback();
- transaction = null;
- context.clear();
- enlisted.clear();
- state = State.FAILED;
- manager.transactionComplete(this);
- manager = null;
- return new MulgaraTransactionException("Transaction rollback triggered", cause);
- case DEACTREF:
- throw new IllegalStateException("Attempt to rollback deactivated transaction");
- case CONSTRUCTEDREF:
- throw new MulgaraTransactionException("Attempt to rollback uninitiated ref'd transaction");
- case CONSTRUCTEDUNREF:
- throw new MulgaraTransactionException("Attempt to rollback uninitiated unref'd transaction");
- case FINISHED:
- throw new MulgaraTransactionException("Attempt to rollback finished transaction");
- case FAILED:
- throw new MulgaraTransactionException("Attempt to rollback failed transaction");
- default:
- throw new MulgaraTransactionException("Unknown state");
- }
- } catch (Throwable th) {
- errorReport("Attempt to rollback failed; initiating cause: ", cause);
- throw abortTransaction("Failed to rollback normally - see log for inititing cause", th);
- } finally {
- report("Leaving implicitRollback");
- }
- }
-
/**
- * Forces the transaction to be abandoned, including bypassing JTA to directly
- * rollback/abort the underlying store-phases if required.
- * Heavilly nested try{}finally{} should guarentee that even JVM errors should
- * not prevent this function from cleaning up anything that can be cleaned up.
- * We have to delegate to the OperationContext the abort() on the resolvers as
- * only it has full knowledge of which resolvers are associated with this
- * transaction.
+ * Used by the TransactionCoordinator/Manager.
*/
- MulgaraTransactionException abortTransaction(String errorMessage, Throwable cause)
- throws MulgaraTransactionException {
- // We need to notify the manager here - this is serious, we
- // need to rollback this transaction, but if we have reached here
- // we have failed to obtain a valid transaction to rollback!
- try {
- try {
- errorReport(errorMessage + " - Aborting", cause);
- } finally { try {
- if (transaction != null) {
- transaction.rollback();
- }
- } finally { try {
- manager.transactionAborted(this);
- } finally { try {
- abortEnlistedResources();
- } finally { try {
- context.clear();
- } finally { try {
- enlisted.clear();
- } finally { try {
- transaction = null;
- } finally { try {
- manager = null;
- } finally {
- state = State.FAILED;
- } } } } } } } }
- return new MulgaraTransactionException(errorMessage + " - Aborting", cause);
- } catch (Throwable th) {
- throw new MulgaraTransactionException(errorMessage + " - Failed to abort cleanly", th);
- } finally {
- report("Leaving abortTransaction");
- }
- }
+ void execute(TransactionOperation to) throws MulgaraTransactionException;
/**
- * Used to bypass JTA and explicitly abort resources behind the scenes.
+ * enlist an XAResource in this transaction - includes an extra method
+ * abort().
*/
- private void abortEnlistedResources() {
- for (EnlistableResource e : enlisted) {
- try {
- e.abort();
- } catch (Throwable th) {
- try {
- errorReport("Error aborting enlistable resource", th);
- } catch (Throwable ignore) { }
- }
- }
- }
-
- void execute(Operation operation,
- ResolverSessionFactory resolverSessionFactory, // FIXME: We shouldn't need this. - only used for backup and restore operations.
- DatabaseMetadata metadata) throws MulgaraTransactionException {
- report("Executing Operation");
- try {
- activate();
- try {
- operation.execute(context,
- context.getSystemResolver(),
- resolverSessionFactory,
- metadata);
- } catch (Throwable th) {
- throw implicitRollback(th);
- } finally {
- deactivate();
- }
- } finally {
- report("Executed Operation");
- }
- }
-
- AnswerOperationResult execute(AnswerOperation ao) throws TuplesException {
- debugReport("Executing AnswerOperation");
- try {
- activate();
- try {
- ao.execute();
- return ao.getResult();
- } catch (Throwable th) {
- throw implicitRollback(th);
- } finally {
- deactivate();
- }
- } catch (MulgaraTransactionException em) {
- throw new TuplesException("Transaction error", em);
- } finally {
- debugReport("Executed AnswerOperation");
- }
- }
-
-
- void execute(TransactionOperation to) throws MulgaraTransactionException {
- report("Executing TransactionOperation");
- try {
- activate();
- try {
- to.execute();
- } catch (Throwable th) {
- throw implicitRollback(th);
- } finally {
- deactivate();
- }
- } finally {
- report("Executed TransactionOperation");
- }
- }
-
- public void enlist(EnlistableResource enlistable) throws MulgaraTransactionException {
- try {
- synchronized (this) {
- if (currentThread == null) {
- throw new MulgaraTransactionException("Transaction not associated with thread");
- } else if (!currentThread.equals(Thread.currentThread())) {
- throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
- }
- }
-
- if (enlisted.contains(enlistable)) {
- return;
- }
-
- switch (state) {
- case ACTUNREF:
- case ACTREF:
- transaction.enlistResource(enlistable.getXAResource());
- enlisted.add(enlistable);
- break;
- case CONSTRUCTEDREF:
- throw new MulgaraTransactionException("Attempt to enlist resource in uninitated ref'd transaction");
- case CONSTRUCTEDUNREF:
- throw new MulgaraTransactionException("Attempt to enlist resource in uninitated unref'd transaction");
- case DEACTREF:
- throw new MulgaraTransactionException("Attempt to enlist resource in unactivated transaction");
- case FINISHED:
- throw new MulgaraTransactionException("Attempt to enlist resource in finished transaction");
- case FAILED:
- throw new MulgaraTransactionException("Attempt to enlist resource in failed transaction");
- }
- } catch (Throwable th) {
- throw implicitRollback(th);
- }
- }
-
- //
- // Used internally
- //
-
- private void checkActivated() throws MulgaraTransactionException {
- synchronized (this) {
- if (currentThread == null) {
- throw new MulgaraTransactionException("Transaction not associated with thread");
- } else if (!currentThread.equals(Thread.currentThread())) {
- throw new MulgaraTransactionException("Concurrent access attempted to transaction: Transaction has NOT been rolledback.");
- }
- }
-
- switch (state) {
- case ACTUNREF:
- case ACTREF:
- if (inuse < 0 || using < 0) {
- throw new MulgaraTransactionException("Reference Failure, using: " + using + ", inuse: " + inuse);
- }
- return;
- case CONSTRUCTEDREF:
- throw new MulgaraTransactionException("Transaction (ref) uninitiated");
- case CONSTRUCTEDUNREF:
- throw new MulgaraTransactionException("Transaction (unref) uninitiated");
- case DEACTREF:
- throw new MulgaraTransactionException("Transaction deactivated");
- case FINISHED:
- throw new MulgaraTransactionException("Transaction is terminated");
- case FAILED:
- throw new MulgaraTransactionException("Transaction is failed", rollbackCause);
- }
- }
-
- private void acquireActivationMutex() {
- activationMutex.lock();
- }
-
- private void releaseActivationMutex() {
- activationMutex.unlock();
- }
-
- protected void finalize() {
- report("GC-finalize");
- if (state != State.FINISHED && state != State.FAILED) {
- errorReport("Finalizing incomplete transaction - aborting...", null);
- try {
- abortTransaction("Transaction finalized while still valid", new Throwable());
- } catch (Throwable th) {
- errorReport("Attempt to abort transaction from finalize failed", th);
- }
- }
-
- if (state != State.FAILED && (inuse != 0 || using != 0)) {
- errorReport("Reference counting error in transaction", null);
- }
-
- if (manager != null || transaction != null) {
- errorReport("Transaction not terminated properly", null);
- }
- }
-
- private void report(String desc) {
- if (logger.isInfoEnabled()) {
- logger.info(desc + ": " + System.identityHashCode(this) + ", state=" + state +
- ", inuse=" + inuse + ", using=" + using);
- }
- }
-
- private void debugReport(String desc) {
- if (logger.isDebugEnabled()) {
- logger.debug(desc + ": " + System.identityHashCode(this) + ", state=" + state +
- ", inuse=" + inuse + ", using=" + using);
- }
- }
-
- private void errorReport(String desc, Throwable cause) {
- logger.error(desc + ": " + System.identityHashCode(this) + ", state=" + state +
- ", inuse=" + inuse + ", using=" + using, cause != null ? cause : new Throwable());
- }
+ public void enlist(EnlistableResource enlistable) throws MulgaraTransactionException;
}
Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionFactory.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,249 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com). Portions created
+ * by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd.
+ * All Rights Reserved.
+ *
+ * Derivation from MulgaraTransactionManager Copyright (c) 2007 Topaz
+ * Foundation under contract by Andrae Muys (mailto:andrae at netymon.com).
+ */
+
+package org.mulgara.resolver;
+
+// Java2 packages
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+// Local packages
+import org.mulgara.query.MulgaraTransactionException;
+import org.mulgara.server.Session;
+import org.mulgara.transaction.TransactionManagerFactory;
+
+/**
+ * Manages transactions within Mulgara.
+ *
+ * see http://mulgara.org/confluence/display/dev/Transaction+Architecture
+ *
+ * Maintains association between Answer's and TransactionContext's.
+ * Manages tracking the ownership of the write-lock.
+ * Maintains the write-queue and any timeout algorithm desired.
+ * Provides new/existing TransactionContext's to DatabaseSession on request.
+ * Note: Returns new context unless Session is currently in a User Demarcated Transaction.
+ *
+ * @created 2006-10-06
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ *
+ * @copyright ©2006 <a href="http://www.netymon.com/">Netymon Pty Ltd</a>
+ *
+ * @licence Open Software License v3.0</a>
+ */
+
+public abstract class MulgaraTransactionFactory {
+ private static final Logger logger =
+ Logger.getLogger(MulgaraTransactionFactory.class.getName());
+
+ protected final MulgaraTransactionManager manager;
+
+ /**
+ * Contains a reference the the current writing transaction IFF it is managed
+ * by this factory. If there is no current writing transaction, or if the
+ * writing transaction is managed by a different factory then it is null.
+ */
+ protected MulgaraTransaction writeTransaction;
+
+ private ReentrantLock mutex;
+
+ protected MulgaraTransactionFactory(MulgaraTransactionManager manager) {
+ this.manager = manager;
+ this.mutex = new ReentrantLock();
+ this.writeTransaction = null;
+ }
+
+
+ /**
+ * Obtain a transaction context associated with a DatabaseSession.
+ *
+ * Either returns the existing context if:
+ * a) we are currently within a recursive call while under implicit XA control
+ * or
+ * b) we are currently within an active user demarcated XA.
+ * otherwise creates a new transaction context and associates it with the
+ * session.
+ */
+ public abstract MulgaraTransaction getTransaction(final DatabaseSession session, boolean write)
+ throws MulgaraTransactionException;
+
+ protected abstract Set<? extends MulgaraTransaction> getTransactionsForSession(DatabaseSession session);
+
+ /**
+ * Rollback, or abort all transactions associated with a DatabaseSession.
+ *
+ * Will only abort the transaction if the rollback attempt fails.
+ */
+ public void closingSession(DatabaseSession session) throws MulgaraTransactionException {
+ acquireMutex();
+ logger.debug("Cleaning up any stale transactions on session close");
+ try {
+ Map<MulgaraTransaction, Throwable> requiresAbort = new HashMap<MulgaraTransaction, Throwable>();
+ try {
+ Throwable error = null;
+
+ if (manager.isHoldingWriteLock(session)) {
+ logger.debug("Session holds write-lock");
+ try {
+ if (writeTransaction != null) {
+ try {
+ logger.warn("Terminating session while holding writelock:" + session + ": " + writeTransaction);
+ writeTransaction.execute(new TransactionOperation() {
+ public void execute() throws MulgaraTransactionException {
+ writeTransaction.heuristicRollback("Session closed while holding write lock");
+ }
+ });
+ } catch (Throwable th) {
+ if (writeTransaction != null) {
+ requiresAbort.put(writeTransaction, th);
+ error = th;
+ }
+ } finally {
+ writeTransaction = null;
+ }
+ }
+ } finally {
+ manager.releaseWriteLock(session);
+ }
+ } else {
+ logger.debug("Session does not hold write-lock");
+ }
+
+ for (MulgaraTransaction transaction : getTransactionsForSession(session)) {
+ try {
+ // This is final so we can create the closure.
+ final MulgaraTransaction xa = transaction;
+ transaction.execute(new TransactionOperation() {
+ public void execute() throws MulgaraTransactionException {
+ xa.heuristicRollback("Rollback due to session close");
+ }
+ });
+ } catch (Throwable th) {
+ requiresAbort.put(transaction, th);
+ if (error == null) {
+ error = th;
+ }
+ }
+ }
+
+ if (error != null) {
+ throw new MulgaraTransactionException("Heuristic rollback failed on session close", error);
+ }
+ } finally {
+ try {
+ abortTransactions(requiresAbort);
+ } catch (Throwable th) {
+ try {
+ logger.error("Error aborting transactions after heuristic rollback failure on session close", th);
+ } catch (Throwable throw_away) { }
+ }
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ /**
+ * Abort as many of the transactions as we can.
+ */
+ protected void abortTransactions(Map<MulgaraTransaction, Throwable> requiresAbort) {
+ try {
+ if (!requiresAbort.isEmpty()) {
+ // At this point the originating exception has been thrown in the caller
+ // so we attempt to ensure it doesn't get superseeded by anything that
+ // might be thrown here while logging any errors.
+ try {
+ logger.error("Heuristic Rollback Failed on session close- aborting");
+ } catch (Throwable throw_away) { } // Logging difficulties.
+
+ try {
+ for (MulgaraTransaction transaction : requiresAbort.keySet()) {
+ try {
+ transaction.abortTransaction("Heuristic Rollback failed on session close",
+ requiresAbort.get(transaction));
+ } catch (Throwable th) {
+ try {
+ logger.error("Error aborting transaction after heuristic rollback failure on session close", th);
+ } catch (Throwable throw_away) { }
+ }
+ }
+ } catch (Throwable th) {
+ try {
+ logger.error("Loop error while aborting transactions after heuristic rollback failure on session close", th);
+ } catch (Throwable throw_away) { }
+ }
+ }
+ } catch (Throwable th) {
+ try {
+ logger.error("Unidentified error while aborting transactions after heuristic rollback failure on session close", th);
+ } catch (Throwable throw_away) { }
+ }
+ }
+
+ /**
+ * Used to replace the built in monitor to allow it to be properly released
+ * during potentially blocking operations. All potentially blocking
+ * operations involve writes, so in these cases the write-lock is reserved
+ * allowing the mutex to be safely released and then reobtained after the
+ * blocking operation concludes.
+ */
+ protected void acquireMutex() {
+ mutex.lock();
+ }
+
+
+ protected void releaseMutex() {
+ if (!mutex.isHeldByCurrentThread()) {
+ throw new IllegalStateException("Attempt to release mutex without holding mutex");
+ }
+
+ mutex.unlock();
+ }
+
+ protected void runWithoutMutex(TransactionOperation proc) throws MulgaraTransactionException {
+ if (!mutex.isHeldByCurrentThread()) {
+ throw new IllegalStateException("Attempt to run procedure without holding mutex");
+ }
+ int holdCount = mutex.getHoldCount();
+ for (int i = 0; i < holdCount; i++) {
+ mutex.unlock();
+ }
+ try {
+ proc.execute();
+ } finally {
+ for (int i = 0; i < holdCount; i++) {
+ mutex.lock();
+ }
+ }
+ }
+}
Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraTransactionManager.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -13,40 +13,34 @@
* (http://www.netymon.com, mailto:mail at netymon.com). Portions created
* by Netymon Pty Ltd are Copyright (c) 2006 Netymon Pty Ltd.
* All Rights Reserved.
+ *
+ * Contributor(s):
+ * Refactoring to focus on write-lock management contributed by Netymon
+ * Pty Ltd on behalf of Topaz Foundation under contract.
*/
package org.mulgara.resolver;
// Java2 packages
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
-import javax.transaction.SystemException;
-import javax.transaction.Transaction;
-import javax.transaction.TransactionManager;
// Third party packages
import org.apache.log4j.Logger;
// Local packages
import org.mulgara.query.MulgaraTransactionException;
-import org.mulgara.server.Session;
import org.mulgara.transaction.TransactionManagerFactory;
/**
- * Manages transactions within Mulgara.
+ * Manages the Write-Lock.
*
- * see http://mulgara.org/confluence/display/dev/Transaction+Architecture
- *
- * Maintains association between Answer's and TransactionContext's.
* Manages tracking the ownership of the write-lock.
- * Maintains the write-queue and any timeout algorithm desired.
* Provides new/existing TransactionContext's to DatabaseSession on request.
* Note: Returns new context unless Session is currently in a User Demarcated Transaction.
- *
+ * Provides a facility to trigger a heuristic rollback of any transactions still
+ * valid on session close.
+ * Maintains the write-queue and any timeout algorithm desired.
*
* @created 2006-10-06
*
@@ -71,457 +65,97 @@
Logger.getLogger(MulgaraTransactionManager.class.getName());
// Write lock is associated with a session.
- private Session currentWritingSession;
- private MulgaraTransaction userTransaction;
- private boolean autoCommit;
+ private DatabaseSession sessionHoldingWriteLock;
- private ReentrantLock mutex;
- private Condition writeLockCondition;
- private Condition reserveCondition;
- private Thread reservingThread;
+ // Used to support write-lock reservation.
+ private DatabaseSession sessionReservingWriteLock;
- /** Set of sessions whose transactions have been rolledback.*/
- private Set<Session> failedSessions;
+ // Used to synchronize access to other fields.
+ private final ReentrantLock mutex;
+ private final Condition writeLockCondition;
- /**
- * Map from transaction to initiating session.
- * FIXME: This is only required for checking while we wait for 1-N.
- * Remove once 1-N is implemented.
- */
- private Map<MulgaraTransaction, Session> sessions;
+ private final MulgaraInternalTransactionFactory internalFactory;
+ private final MulgaraExternalTransactionFactory externalFactory;
- /**
- * Map from initiating session to set of transactions.
- * Used to clean-up transactions upon session close.
- */
- private Map<Session, Set<MulgaraTransaction>> transactions;
-
- /** Map of threads to active transactions. */
- private Map<Thread, MulgaraTransaction> activeTransactions;
-
- private final TransactionManager transactionManager;
-
public MulgaraTransactionManager(TransactionManagerFactory transactionManagerFactory) {
- this.currentWritingSession = null;
- this.userTransaction = null;
- this.autoCommit = true;
+ this.sessionHoldingWriteLock = null;
+ this.sessionReservingWriteLock = null;
this.mutex = new ReentrantLock();
this.writeLockCondition = this.mutex.newCondition();
- this.reserveCondition = this.mutex.newCondition();
- this.reservingThread = null;
- this.failedSessions = new HashSet<Session>();
- this.sessions = new HashMap<MulgaraTransaction, Session>();
- this.transactions = new HashMap<Session, Set<MulgaraTransaction>>();
- this.activeTransactions = new HashMap<Thread, MulgaraTransaction>();
-
- this.transactionManager = transactionManagerFactory.newTransactionManager();
+ this.internalFactory = new MulgaraInternalTransactionFactory(this, transactionManagerFactory);
+ this.externalFactory = new MulgaraExternalTransactionFactory(this);
}
- /**
- * Allows DatabaseSession to initiate/obtain a transaction.
- * <ul>
- * <li>If the Session holds the write lock, return the current Write-Transaction.</li>
- * <li>If the Session does not hold the write lock and requests a read-only transaction,
- * create a new ro-transaction object and return it.</li>
- * <li>If the Session does not hold the write lock and requests a read-write transaction,
- * obtain the write-lock, create a new transaction object and return it.</li>
- * </ul>
- */
- public MulgaraTransaction getTransaction(DatabaseSession session, boolean write) throws MulgaraTransactionException {
- acquireMutex();
- try {
- if (session == currentWritingSession) {
- return userTransaction;
- }
- try {
- MulgaraTransaction transaction = write ?
- obtainWriteLock(session) :
- new MulgaraTransaction(this, session.newOperationContext(false));
+ MulgaraInternalTransactionFactory getInternalFactory() {
+ return internalFactory;
+ }
- sessions.put(transaction, session);
-
- if (!transactions.containsKey(session)) {
- transactions.put(session, new HashSet<MulgaraTransaction>());
- }
- transactions.get(session).add(transaction);
-
- return transaction;
- } catch (MulgaraTransactionException em) {
- throw em;
- } catch (Exception e) {
- throw new MulgaraTransactionException("Error creating transaction", e);
- }
- } finally {
- releaseMutex();
- }
+ MulgaraExternalTransactionFactory getExternalFactory() {
+ return externalFactory;
}
/**
* Obtains the write lock.
- * Must hold readMutex on entry - but will drop readMutex if
*/
- private MulgaraTransaction obtainWriteLock(DatabaseSession session)
- throws MulgaraTransactionException {
- while (writeLockHeld() || writeLockReserved()) {
- try {
- writeLockCondition.await();
- } catch (InterruptedException ei) {
- throw new MulgaraTransactionException("Interrupted while waiting for write lock", ei);
- }
- }
-
- try {
- currentWritingSession = session;
- userTransaction = new MulgaraTransaction(this, session.newOperationContext(true));
- return userTransaction;
- } catch (Throwable th) {
- releaseWriteLock();
- throw new MulgaraTransactionException("Error while obtaining write-lock", th);
- }
- }
-
- private void releaseWriteLock() {
- // Calling this method multiple times is safe as the lock cannot be obtained
- // between calls as this method is private, and all calling methods are
- // synchronized.
- currentWritingSession = null;
- userTransaction = null;
- writeLockCondition.signal();
- }
-
- public void commit(DatabaseSession session) throws MulgaraTransactionException {
+ void obtainWriteLock(DatabaseSession session) throws MulgaraTransactionException {
acquireMutex();
try {
- reserveWriteLock();
- if (failedSessions.contains(session)) {
- throw new MulgaraTransactionException("Attempting to commit failed exception");
- } else if (session != currentWritingSession) {
- throw new MulgaraTransactionException(
- "Attempting to commit while not the current writing transaction");
+ if (sessionHoldingWriteLock == session) {
+ return;
}
- setAutoCommit(session, true);
- setAutoCommit(session, false);
- } finally {
- releaseMutex();
- }
- }
-
-
- /**
- * This is an explicit, user-specified rollback.
- *
- * This needs to be distinguished from an implicit rollback triggered by failure.
- */
- public void rollback(DatabaseSession session) throws MulgaraTransactionException {
- acquireMutex();
- try {
- reserveWriteLock();
- if (session == currentWritingSession) {
+ while (writeLockHeld() || (writeLockReserved() && !writeLockReserved(session))) {
try {
- userTransaction.execute(new TransactionOperation() {
- public void execute() throws MulgaraTransactionException {
- userTransaction.explicitRollback();
- }
- });
- if (userTransaction != null) {
- // transaction referenced by something - need to explicitly end it.
- userTransaction.abortTransaction("Rollback failed",
- new MulgaraTransactionException("Rollback failed to terminate write transaction"));
- }
- } finally {
- failedSessions.add(session);
- releaseWriteLock();
- setAutoCommit(session, false);
+ writeLockCondition.await();
+ } catch (InterruptedException ei) {
+ throw new MulgaraTransactionException("Interrupted while waiting for write lock", ei);
}
- } else if (failedSessions.contains(session)) {
- failedSessions.remove(session);
- setAutoCommit(session, false);
- } else {
- throw new MulgaraTransactionException(
- "Attempt to rollback while not in the current writing transaction");
}
- } finally {
- releaseMutex();
- }
- }
- public void setAutoCommit(DatabaseSession session, boolean autoCommit)
- throws MulgaraTransactionException {
- acquireMutex();
- try {
- if (session == currentWritingSession && failedSessions.contains(session)) {
- userTransaction.abortTransaction("Session failed and transaction not finalized",
- new MulgaraTransactionException("Failed Session in setAutoCommit"));
+ if (logger.isDebugEnabled()) {
+ logger.debug("Obtaining write lock", new Throwable());
}
-
- if (session == currentWritingSession || failedSessions.contains(session)) {
- if (autoCommit) {
- // AutoCommit off -> on === branch on current state of transaction.
- if (session == currentWritingSession) {
- // Within active transaction - commit and finalise.
- try {
- runWithoutMutex(new TransactionOperation() {
- public void execute() throws MulgaraTransactionException {
- userTransaction.execute(new TransactionOperation() {
- public void execute() throws MulgaraTransactionException {
- userTransaction.dereference();
- userTransaction.commitTransaction();
- }
- });
- }
- });
- } finally {
- releaseWriteLock();
- this.autoCommit = true;
- }
- } else if (failedSessions.contains(session)) {
- // Within failed transaction - cleanup.
- failedSessions.remove(session);
- }
- } else {
- logger.info("Attempt to set autocommit false twice");
- // AutoCommit off -> off === no-op. Log info.
- }
- } else {
- if (autoCommit) {
- // AutoCommit on -> on === no-op. Log info.
- logger.info("Attempting to set autocommit true without setting it false");
- } else {
- // AutoCommit on -> off == Start new transaction.
- userTransaction = getTransaction(session, true);
- userTransaction.reference();
- this.autoCommit = false;
- }
- }
+ sessionHoldingWriteLock = session;
} finally {
releaseMutex();
}
}
- public void rollbackCurrentTransactions(Session session) throws MulgaraTransactionException {
+ boolean isHoldingWriteLock(DatabaseSession session) {
acquireMutex();
try {
- try {
- if (failedSessions.contains(session)) {
- failedSessions.remove(session);
- return;
- }
-
- Throwable error = null;
-
- try {
- if (session == currentWritingSession) {
- logger.warn("Terminating session while holding writelock:" + session + ": " + userTransaction);
- userTransaction.execute(new TransactionOperation() {
- public void execute() throws MulgaraTransactionException {
- throw new MulgaraTransactionException("Terminating session while holding writelock");
- }
- });
- }
- } catch (Throwable th) {
- error = th;
- }
-
- if (transactions.containsKey(session)) {
- for (MulgaraTransaction transaction : transactions.get(session)) {
- try {
- transaction.execute(new TransactionOperation() {
- public void execute() throws MulgaraTransactionException {
- throw new MulgaraTransactionException("Rolling back transactions due to session close");
- }
- });
- } catch (MulgaraTransactionException em) {
- // ignore.
- } catch (Throwable th) {
- if (error == null) {
- error = th;
- }
- }
- }
- }
-
- if (error != null) {
- if (error instanceof MulgaraTransactionException) {
- throw (MulgaraTransactionException)error;
- } else {
- throw new MulgaraTransactionException("Error in rollback on session close", error);
- }
- }
- } finally {
- if (transactions.containsKey(session)) {
- logger.error("Error in transaction rollback due to session close - aborting");
- abortCurrentTransactions(session);
- }
- }
+ return sessionHoldingWriteLock == session;
} finally {
releaseMutex();
}
}
- private void abortCurrentTransactions(Session session) throws MulgaraTransactionException {
- acquireMutex();
- try {
- try {
- Throwable error = null;
- for (MulgaraTransaction transaction : transactions.get(session)) {
- try {
- transaction.abortTransaction("Transaction still valid on session close", new Throwable());
- } catch (Throwable th) {
- try {
- if (error == null) {
- error = th;
- }
- } catch (Throwable throw_away) {}
- }
- }
- if (error != null) {
- if (error instanceof MulgaraTransactionException) {
- throw (MulgaraTransactionException)error;
- } else {
- throw new MulgaraTransactionException("Error in rollback on session close", error);
- }
- }
- } finally {
- if (session == currentWritingSession) {
- logger.error("Failed to abort write-transaction on session close - Server restart required");
- }
- }
- } finally {
- releaseMutex();
- }
- }
-
- //
- // Transaction livecycle callbacks.
- //
-
- public Transaction transactionStart(MulgaraTransaction transaction) throws MulgaraTransactionException {
+ void releaseWriteLock(DatabaseSession session) throws MulgaraTransactionException {
acquireMutex();
try {
- try {
- logger.info("Beginning Transaction");
- if (activeTransactions.get(Thread.currentThread()) != null) {
- throw new MulgaraTransactionException(
- "Attempt to start transaction in thread with exiting active transaction.");
- } else if (activeTransactions.containsValue(transaction)) {
- throw new MulgaraTransactionException("Attempt to start transaction twice");
- }
-
- transactionManager.begin();
- Transaction jtaTrans = transactionManager.getTransaction();
-
- activeTransactions.put(Thread.currentThread(), transaction);
-
- return jtaTrans;
- } catch (Exception e) {
- throw new MulgaraTransactionException("Transaction Begin Failed", e);
+ if (sessionHoldingWriteLock == null) {
+ return;
}
- } finally {
- releaseMutex();
- }
- }
-
- public void transactionResumed(MulgaraTransaction transaction, Transaction jtaXA)
- throws MulgaraTransactionException {
- acquireMutex();
- try {
- if (activeTransactions.get(Thread.currentThread()) != null) {
- throw new MulgaraTransactionException(
- "Attempt to resume transaction in already activated thread");
- } else if (activeTransactions.containsValue(transaction)) {
- throw new MulgaraTransactionException("Attempt to resume active transaction");
+ if (sessionHoldingWriteLock != session) {
+ throw new MulgaraTransactionException("Attempted to release write lock being held by another session");
}
-
- try {
- transactionManager.resume(jtaXA);
- activeTransactions.put(Thread.currentThread(), transaction);
- } catch (Exception e) {
- throw new MulgaraTransactionException("Resume Failed", e);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Releasing writelock", new Throwable());
}
+ sessionHoldingWriteLock = null;
+ writeLockCondition.signal();
} finally {
releaseMutex();
}
}
- public Transaction transactionSuspended(MulgaraTransaction transaction)
- throws MulgaraTransactionException {
- acquireMutex();
- try {
- try {
- if (transaction != activeTransactions.get(Thread.currentThread())) {
- throw new MulgaraTransactionException(
- "Attempt to suspend transaction from outside thread");
- }
- if (autoCommit && transaction == userTransaction) {
- logger.error("Attempt to suspend write transaction without setting AutoCommit Off");
- throw new MulgaraTransactionException(
- "Attempt to suspend write transaction without setting AutoCommit Off");
- }
-
- Transaction xa = transactionManager.suspend();
- activeTransactions.remove(Thread.currentThread());
-
- return xa;
- } catch (Throwable th) {
- logger.error("Attempt to suspend failed", th);
- try {
- transactionManager.setRollbackOnly();
- } catch (Throwable t) {
- logger.error("Attempt to setRollbackOnly() failed", t);
- }
- throw new MulgaraTransactionException("Suspend failed", th);
- }
- } finally {
- releaseMutex();
- }
- }
-
- public void transactionComplete(MulgaraTransaction transaction) {
- acquireMutex();
- try {
- if (transaction == userTransaction) {
- releaseWriteLock();
- }
-
- activeTransactions.remove(Thread.currentThread());
- Session session = (Session)sessions.get(transaction);
- sessions.remove(transaction);
- transactions.remove(session);
- } finally {
- releaseMutex();
- }
- }
-
- public void transactionAborted(MulgaraTransaction transaction) {
- acquireMutex();
- try {
- try {
- // Make sure this cleans up the transaction metadata - this transaction is DEAD!
- if (transaction == userTransaction) {
- failedSessions.add(currentWritingSession);
- }
- transactionComplete(transaction);
- } catch (Throwable th) {
- // FIXME: This should probably abort the entire server after logging the error!
- logger.error("Error managing transaction abort", th);
- }
- } finally {
- releaseMutex();
- }
- }
-
public void setTransactionTimeout(int transactionTimeout) {
- try {
- transactionManager.setTransactionTimeout(transactionTimeout);
- } catch (SystemException es) {
- logger.warn("Unable to set transaction timeout: " + transactionTimeout, es);
- }
+ internalFactory.setTransactionTimeout(transactionTimeout);
}
/**
@@ -538,33 +172,51 @@
/**
* Used to reserve the write lock during a commit or rollback.
+ * Should only be used by a transaction manager.
*/
- private void reserveWriteLock() throws MulgaraTransactionException {
- if (!mutex.isHeldByCurrentThread()) {
- throw new IllegalStateException("Attempt to set modify without holding mutex");
- }
+ void reserveWriteLock(DatabaseSession session) throws MulgaraTransactionException {
+ acquireMutex();
+ try {
+ if (session != sessionReservingWriteLock && session != sessionHoldingWriteLock) {
+ throw new IllegalStateException("Attempt to reserve writelock without holding writelock");
+ }
+ if (session != sessionReservingWriteLock && sessionReservingWriteLock != null) {
+ throw new IllegalStateException("Attempt to reserve writelock when writelock already reserved");
+ }
- if (Thread.currentThread().equals(reservingThread)) {
- return;
+ sessionReservingWriteLock = session;
+ } finally {
+ releaseMutex();
}
+ }
- while (reservingThread != null) {
- try {
- reserveCondition.await();
- } catch (InterruptedException ei) {
- throw new MulgaraTransactionException("Thread interrupted while reserving write lock", ei);
- }
- }
- reservingThread = Thread.currentThread();
+ boolean writeLockReserved() {
+ return sessionReservingWriteLock != null;
}
- private boolean writeLockReserved() {
- // TRUE iff there is a reserving thread AND it is different thread to the current thread.
- return reservingThread != null && !Thread.currentThread().equals(reservingThread);
+ boolean writeLockReserved(DatabaseSession session) {
+ return session == sessionReservingWriteLock;
}
+ void releaseReserve(DatabaseSession session) {
+ acquireMutex();
+ try {
+ if (!writeLockReserved()) {
+ return;
+ }
+ if (!writeLockReserved(session)) {
+ throw new IllegalStateException("Attempt to release reserve without holding reserve");
+ }
+
+ sessionReservingWriteLock = null;
+ writeLockCondition.signal();
+ } finally {
+ releaseMutex();
+ }
+ }
+
private boolean writeLockHeld() {
- return currentWritingSession != null;
+ return sessionHoldingWriteLock != null;
}
private void releaseMutex() {
@@ -572,28 +224,59 @@
throw new IllegalStateException("Attempt to release mutex without holding mutex");
}
- if (mutex.getHoldCount() == 1 && Thread.currentThread().equals(reservingThread)) {
- reservingThread = null;
- reserveCondition.signal();
- }
-
mutex.unlock();
}
- private void runWithoutMutex(TransactionOperation proc) throws MulgaraTransactionException {
- if (!mutex.isHeldByCurrentThread()) {
- throw new IllegalStateException("Attempt to run procedure without holding mutex");
+ public void closingSession(DatabaseSession session) throws MulgaraTransactionException {
+ // Calls to final fields do not require mutex. As both of these calls
+ // potentially call back into the manager, calling these while holding the
+ // lock can invalidate lock-ordering and result in a deadlock.
+ Throwable error = null;
+ try {
+ internalFactory.closingSession(session);
+ } catch (Throwable th) {
+ logger.error("Error signalling session-close to internal xa-factory", th);
+ error = (error == null) ? th : error;
}
- int holdCount = mutex.getHoldCount();
- for (int i = 0; i < holdCount; i++) {
- mutex.unlock();
+
+ try {
+ externalFactory.closingSession(session);
+ } catch (Throwable th) {
+ logger.error("Error signalling session-close to external xa-factory", th);
+ error = (error == null) ? th : error;
}
+
+ // This code should not be required, but is there to ensure the manager is
+ // reset regardless of errors in the factories.
+ acquireMutex();
try {
- proc.execute();
- } finally {
- for (int i = 0; i < holdCount; i++) {
- mutex.lock();
+ if (writeLockReserved(session)) {
+ try {
+ releaseReserve(session);
+ } catch (Throwable th) {
+ logger.error("Error releasing reserve on force-close", th);
+ error = (error == null) ? th : error;
+ }
}
+
+ if (isHoldingWriteLock(session)) {
+ try {
+ releaseWriteLock(session);
+ } catch (Throwable th) {
+ logger.error("Error releasing write-lock on force-close", th);
+ error = (error == null) ? th : error;
+ }
+ }
+
+ if (error != null) {
+ if (error instanceof MulgaraTransactionException) {
+ throw (MulgaraTransactionException)error;
+ } else {
+ throw new MulgaraTransactionException("Error force releasing write-lock", error);
+ }
+ }
+ } finally {
+ releaseMutex();
}
}
}
Copied: trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java (from rev 690, branches/mgr-73/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java)
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java (rev 0)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/MulgaraXAResourceContext.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,536 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com) under contract to
+ * Topaz Foundation. Portions created under this contract are
+ * Copyright (c) 2007 Topaz Foundation
+ * All Rights Reserved.
+ */
+
+package org.mulgara.resolver;
+
+// Java2 packages
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.locks.ReentrantLock;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+// Local packages
+import org.mulgara.query.MulgaraTransactionException;
+import org.mulgara.server.ResourceManagerInstanceAdaptor;
+import org.mulgara.util.Assoc1toNMap;
+
+/**
+ * Provides an external JTA-compliant TransactionManager with the ability to
+ * control Mulgara Transactions.
+ *
+ * @created 2007-11-07
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ *
+ * @copyright ©2006 <a href="http://www.topazproject.org/">Topaz Project</a>
+ *
+ * @licence Open Software License v3.0</a>
+ */
+
+public class MulgaraXAResourceContext {
+ private static final Logger logger =
+ Logger.getLogger(MulgaraXAResourceContext.class.getName());
+ /**
+ * Map from keyed from the {@link Integer} value of the various flags
+ * defined in {@link XAResource} and mapping to the formatted name for that
+ * flag.
+ */
+ private final static Map<Integer, String> flagMap = new HashMap<Integer, String>();
+
+ static {
+ flagMap.put(XAResource.TMENDRSCAN, "TMENDRSCAN");
+ flagMap.put(XAResource.TMFAIL, "TMFAIL");
+ flagMap.put(XAResource.TMJOIN, "TMJOIN");
+ flagMap.put(XAResource.TMONEPHASE, "TMONEPHASE");
+ flagMap.put(XAResource.TMRESUME, "TMRESUME");
+ flagMap.put(XAResource.TMSTARTRSCAN, "TMSTARTRSCAN");
+ flagMap.put(XAResource.TMSUCCESS, "TMSUCCESS");
+ flagMap.put(XAResource.TMSUSPEND, "TMSUSPEND");
+ }
+
+ private final MulgaraExternalTransactionFactory factory;
+
+ protected final DatabaseSession session;
+
+ private final Assoc1toNMap<MulgaraExternalTransaction, Xid> xa2xid;
+
+ private final ReentrantLock mutex;
+
+ private final UUID uniqueId;
+
+ MulgaraXAResourceContext(MulgaraExternalTransactionFactory factory, DatabaseSession session) {
+ logger.info("Creating MulgaraXAResource");
+ this.factory = factory;
+ this.session = session;
+ this.xa2xid = new Assoc1toNMap<MulgaraExternalTransaction, Xid>();
+ this.mutex = new ReentrantLock();
+ this.uniqueId = UUID.randomUUID();
+ }
+
+ public XAResource getResource(boolean writing) {
+ return new MulgaraXAResource(writing);
+ }
+
+ private class MulgaraXAResource implements XAResource,
+ ResourceManagerInstanceAdaptor {
+ private final boolean writing;
+
+ public MulgaraXAResource(boolean writing) {
+ this.writing = writing;
+ }
+
+ /**
+ * Commit transaction identified by xid.
+ *
+ * Transaction must be Idle, Prepared, or Heuristically-Completed.
+ * If transaction not Heuristically-Completed we are required to finish it,
+ * clean up, and forget it.
+ * If transaction is Heuristically-Completed we throw an exception and wait
+ * for a call to forget().
+ */
+ public void commit(Xid xid, boolean onePhase) throws XAException {
+ acquireMutex();
+ try {
+ xid = convertXid(xid);
+ logger.info("Performing commit: " + parseXid(xid));
+ MulgaraExternalTransaction xa = xa2xid.get1(xid);
+ if (xa == null) {
+ throw new XAException(XAException.XAER_NOTA);
+ } else if (xa.isHeuristicallyRollbacked()) {
+ // HEURRB causes difficulties with JOTM - so throw the less precise
+ // but still correct RBROLLBACK.
+ // Note: Found the problem here - The J2EE Connector Architecture
+ // 7.6.2.2 requires an XA_RB* exception in the case of 1PC and 7.6.2.5
+ // implies that HEURRB is not permitted during 2PC - this seems broken
+ // to me, but that's the spec.
+// throw new XAException(XAException.XA_HEURRB);
+ throw new XAException(XAException.XA_RBROLLBACK);
+ } else if (xa.isHeuristicallyCommitted()) {
+ throw new XAException(XAException.XA_HEURCOM);
+ }
+
+ if (onePhase) {
+ try {
+ xa.prepare(xid);
+ } catch (XAException ex) {
+ if (ex.errorCode != XAException.XA_RDONLY) {
+ doRollback(xa, xid);
+ }
+ // Note: XA spec requires us to forget about transactions that fail
+ // during commit. doRollback throws exception under Heuristic
+ // Completion - when we do want to remember transaction.
+ xa2xid.remove1(xa);
+ throw ex;
+ }
+ }
+
+ try {
+ xa.commit(xid);
+ xa2xid.remove1(xa);
+ } catch (XAException ex) {
+ // We are not allowed to forget this transaction if we completed
+ // heuristically.
+ switch (ex.errorCode) {
+ case XAException.XA_HEURHAZ:
+ case XAException.XA_HEURCOM:
+ case XAException.XA_HEURRB:
+ case XAException.XA_HEURMIX:
+ throw ex;
+ default:
+ xa2xid.remove1(xa);
+ throw ex;
+ }
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ /**
+ * Deactivate a transaction.
+ *
+ * TMSUCCESS: Move to Idle and await call to rollback, prepare, or commit.
+ * TMFAIL: Move to RollbackOnly; await call to rollback.
+ * TMSUSPEND: Move to Idle and await start(TMRESUME) or end(TMSUCCESS|FAIL)
+ *
+ * In all cases disassociate from current session.
+ */
+ public void end(Xid xid, int flags) throws XAException {
+ acquireMutex();
+ try {
+ xid = convertXid(xid);
+ logger.info("Performing end(" + formatFlags(flags) + "): " + parseXid(xid));
+ MulgaraExternalTransaction xa = xa2xid.get1(xid);
+ if (xa == null) {
+ throw new XAException(XAException.XAER_NOTA);
+ }
+ switch (flags) {
+ case TMFAIL:
+ doRollback(xa, xid);
+ break;
+ case TMSUCCESS:
+ if (xa.isHeuristicallyRollbacked()) {
+ throw new XAException(XAException.XA_RBPROTO);
+ }
+ break;
+ case TMSUSPEND: // Should I be tracking the xid's state to ensure
+ // conformance with the X/Open state diagrams?
+ break;
+ default:
+ logger.error("Invalid flag passed to end() : " + flags);
+ throw new XAException(XAException.XAER_INVAL);
+ }
+
+ try {
+ // If XA is currently associated with session, disassociate it.
+ factory.disassociateTransaction(session, xa);
+ } catch (MulgaraTransactionException em) {
+ logger.error("Error disassociating transaction from session", em);
+ throw new XAException(XAException.XAER_PROTO);
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public void forget(Xid xid) throws XAException {
+ acquireMutex();
+ try {
+ xid = convertXid(xid);
+ logger.info("Performing forget: " + parseXid(xid));
+ MulgaraExternalTransaction xa = xa2xid.get1(xid);
+ if (xa == null) {
+ throw new XAException(XAException.XAER_NOTA);
+ }
+ try {
+ if (!xa.isHeuristicallyRollbacked()) {
+ try {
+ xa.abortTransaction("External XA Manager specified 'forget'", new Throwable());
+ } catch (MulgaraTransactionException em) {
+ logger.error("Failed to abort transaction in forget", em);
+ throw new XAException(XAException.XAER_RMERR);
+ }
+ }
+ } finally {
+ xa2xid.remove1(xa);
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public int getTransactionTimeout() {
+ acquireMutex();
+ try {
+ logger.info("Performing getTransactionTimeout");
+ return 3600;
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ public boolean isSameRM(XAResource xares) {
+ acquireMutex();
+ try {
+ logger.info("Performing isSameRM");
+ if (!xares.getClass().equals(MulgaraXAResource.class)) {
+ return false;
+ } else {
+ // Based on X/Open-XA-TP section 3.2 I believe a 'Resource Manager
+ // Instance' corresponds to a session, as each session 'supports
+ // independent transaction completion'.
+ return session == ((MulgaraXAResource)xares).getSession();
+ }
+ } finally {
+ releaseMutex();
+ }
+ }
+
+
+ public int prepare(Xid xid) throws XAException {
+ acquireMutex();
+ try {
+ xid = convertXid(xid);
+ logger.info("Performing prepare: " + parseXid(xid));
+ MulgaraExternalTransaction xa = xa2xid.get1(xid);
+ if (xa == null) {
+ throw new XAException(XAException.XAER_NOTA);
+ } else if (xa.isRollbacked()) {
+ throw new XAException(XAException.XA_RBROLLBACK);
+ }
+
+ xa.prepare(xid);
+
+ return XA_OK;
+ } finally {
+ releaseMutex();
+ }
+ }
+
+ /**
+ * We don't currently support recover.
+ * FIXME: We should at least handle the case where we are asked to recover
+ * when we haven't crashed.
+ */
+ public Xid[] recover(int flag) {
+ acquireMutex();
+ try {
+ logger.info("Performing recover");
+ return new Xid[] {};
+ } finally {
+ releaseMutex();
+ }
+ }
+
+
+ public void rollback(Xid xid) throws XAException {
+ acquireMutex();
+ try {
+ xid = convertXid(xid);
+ logger.info("Performing rollback: " + parseXid(xid));
+ MulgaraExternalTransaction xa = xa2xid.get1(xid);
+ if (xa == null) {
+ throw new XAException(XAException.XAER_NOTA);
+ }
+
+ doRollback(xa, xid);
+ // If we don't throw a Heuristic Exception we need to forget this
+ // transaction. doRollback only throws Heuristic Exceptions.
+ xa2xid.remove1(xa);
+ } finally {
+ releaseMutex();
+ }
+ }
+
+
+ /**
+ * Performs rollback. Only throws exception if transaction is subject to
+ * Heuristic Completion.
+ */
+ private void doRollback(MulgaraExternalTransaction xa, Xid xid) throws XAException {
+ if (xa.isHeuristicallyRollbacked()) {
+ logger.warn("Attempted to rollback heuristically rollbacked transaction: " + xa.getHeuristicCode());
+ throw new XAException(xa.getHeuristicCode());
+ } else if (!xa.isRollbacked()) {
+ xa.rollback(xid);
+ }
+ }
+
+
+ public boolean setTransactionTimeout(int seconds) {
+ acquireMutex();
+ try {
+ logger.info("Performing setTransactionTimeout");
+ return false;
+ } finally {
+ releaseMutex();
+ }
+ }
+
+
+ public void start(Xid xid, int flags) throws XAException {
+ acquireMutex();
+ try {
+ xid = convertXid(xid);
+ logger.info("Performing start(" + formatFlags(flags) + "): " + parseXid(xid));
+ switch (flags) {
+ case TMNOFLAGS:
+ if (xa2xid.containsN(xid)) {
+ throw new XAException(XAException.XAER_DUPID);
+ } else if (factory.hasAssociatedTransaction(session)) {
+ throw new XAException(XAException.XA_RBDEADLOCK);
+ } else {
+ // FIXME: Need to consider read-only transactions here.
+ try {
+ MulgaraExternalTransaction xa = factory.createTransaction(session, xid, writing);
+ xa2xid.put(xa, xid);
+ } catch (MulgaraTransactionException em) {
+ logger.error("Failed to create transaction", em);
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+ break;
+ case TMJOIN:
+ if (!factory.hasAssociatedTransaction(session)) {
+ throw new XAException(XAException.XAER_NOTA);
+ } else if (!factory.getAssociatedTransaction(session).getXid().equals(xid)) {
+ throw new XAException(XAException.XAER_OUTSIDE);
+ }
+ break;
+ case TMRESUME:
+ MulgaraExternalTransaction xa = xa2xid.get1(xid);
+ if (xa == null) {
+ throw new XAException(XAException.XAER_NOTA);
+ } else if (xa.isRollbacked()) {
+ throw new XAException(XAException.XA_RBROLLBACK);
+ } else {
+ if (!factory.associateTransaction(session, xa)) {
+ // session already associated with a transaction.
+ throw new XAException(XAException.XAER_PROTO);
+ }
+ }
+ break;
+ }
+ } finally {
+ releaseMutex();
+ }
+
+ }
+
+ public Serializable getRMId() {
+ return uniqueId;
+ }
+
+ /**
+ * Required only because Java has trouble with accessing fields from
+ * inner-classes.
+ */
+ private DatabaseSession getSession() { return session; }
+ }
+
+ /**
+ * Used to replace the built in monitor to allow it to be properly released
+ * during potentially blocking operations. All potentially blocking
+ * operations involve writes, so in these cases the write-lock is reserved
+ * allowing the mutex to be safely released and then reobtained after the
+ * blocking operation concludes.
+ */
+ protected void acquireMutex() {
+ mutex.lock();
+ }
+
+
+ protected void releaseMutex() {
+ if (!mutex.isHeldByCurrentThread()) {
+ throw new IllegalStateException("Attempt to release mutex without holding mutex");
+ }
+
+ mutex.unlock();
+ }
+
+ public static String parseXid(Xid xid) {
+ return xid.toString();
+ }
+
+ private static InternalXid convertXid(Xid xid) {
+ return new InternalXid(xid);
+ }
+
+ /**
+ * Provides an Xid that compares equal by value.
+ */
+ private static class InternalXid implements Xid {
+ private byte[] bq;
+ private int fi;
+ private byte[] gtid;
+
+ public InternalXid(Xid xid) {
+ byte[] tbq = xid.getBranchQualifier();
+ byte[] tgtid = xid.getGlobalTransactionId();
+ this.bq = new byte[tbq.length];
+ this.fi = xid.getFormatId();
+ this.gtid = new byte[tgtid.length];
+ System.arraycopy(tbq, 0, this.bq, 0, tbq.length);
+ System.arraycopy(tgtid, 0, this.gtid, 0, tgtid.length);
+ }
+
+ public byte[] getBranchQualifier() {
+ return bq;
+ }
+
+ public int getFormatId() {
+ return fi;
+ }
+
+ public byte[] getGlobalTransactionId() {
+ return gtid;
+ }
+
+ public int hashCode() {
+ return Arrays.hashCode(bq) ^ fi ^ Arrays.hashCode(gtid);
+ }
+
+ public boolean equals(Object rhs) {
+ if (!(rhs instanceof InternalXid)) {
+ return false;
+ } else {
+ InternalXid rhx = (InternalXid)rhs;
+ return this.fi == rhx.fi &&
+ Arrays.equals(this.bq, rhx.bq) &&
+ Arrays.equals(this.gtid, rhx.gtid);
+ }
+ }
+
+ public String toString() {
+ return ":" + fi + ":" + Arrays.hashCode(gtid) + ":" + Arrays.hashCode(bq) + ":";
+ }
+ }
+
+ /**
+ * Format bitmasks defined by {@link XAResource}.
+ *
+ * @param flags a bitmask composed from the constants defined in
+ * {@link XAResource}
+ * @return a formatted representation of the <var>flags</var>
+ */
+ private static String formatFlags(int flags)
+ {
+ // Short-circuit evaluation if we've been explicitly passed no flags
+ if (flags == XAResource.TMNOFLAGS) {
+ return "TMNOFLAGS";
+ }
+
+ StringBuffer buffer = new StringBuffer();
+
+ // Add any flags that are present
+ for (Map.Entry<Integer, String> entry : flagMap.entrySet()) {
+ int flag = entry.getKey();
+
+ // If this flag is present, add it to the formatted output and remove
+ // from the bitmask
+ if ((flag & flags) == flag) {
+ if (buffer.length() > 0) {
+ buffer.append("|");
+ }
+ buffer.append(entry.getValue());
+ flags &= ~flag;
+ }
+ }
+
+ // We would expect to have removed all flags by this point
+ // If there's some unknown flag we've missed, format it as hexadecimal
+ if (flags != 0) {
+ if (buffer.length() > 0) {
+ buffer.append(",");
+ }
+ buffer.append("0x").append(Integer.toHexString(flags));
+ }
+
+ return buffer.toString();
+ }
+}
Modified: trunk/src/jar/resolver/java/org/mulgara/resolver/XADatabaseSessionUnitTest.java
===================================================================
--- trunk/src/jar/resolver/java/org/mulgara/resolver/XADatabaseSessionUnitTest.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver/java/org/mulgara/resolver/XADatabaseSessionUnitTest.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -611,7 +611,7 @@
// Evaluate the query
Answer answer = new ArrayAnswer(session.query(new Query(
- (List<Object>)(List)test.selectList, // SELECT
+ test.selectList, // SELECT
test.model, // FROM
test.query, // WHERE
null, // HAVING
Modified: trunk/src/jar/resolver-distributed/java/org/mulgara/resolver/distributed/NetworkDelegator.java
===================================================================
--- trunk/src/jar/resolver-distributed/java/org/mulgara/resolver/distributed/NetworkDelegator.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-distributed/java/org/mulgara/resolver/distributed/NetworkDelegator.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -198,7 +198,7 @@
);
// convert the variable set to a variable list - add types via unchecked casts
- List<Object> variables = new ArrayList<Object>((Set<Variable>)globalConstraint.getVariables());
+ List<Variable> variables = new ArrayList<Variable>((Set<Variable>)globalConstraint.getVariables());
// build the new query
return new Query(variables, new ModelResource(model.getURI()), globalConstraint, null, Collections.EMPTY_LIST, null, 0, new UnconstrainedAnswer());
}
Modified: trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolver.java
===================================================================
--- trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolver.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolver.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
- * Contributor(s): N/A.
+ * Contributor(s):
+ * Migration to AbstractXAResource copyright 2008 The Topaz Foundation
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
@@ -84,8 +85,10 @@
*/
private final Set<Stating> statingSet;
- private XAResolverSession xaResolverSession;
+ private final XAResource xares;
+ private final XAResolverSession xaResolverSession;
+
//
// Constructors
//
@@ -105,34 +108,30 @@
* is {@link NodePool#NONE}
*/
MemoryResolver(ResolverSession resolverSession,
- Resolver systemResolver,
long rdfType,
- long systemModel,
URI modelTypeURI,
Set<Stating> statingSet)
throws ResolverFactoryException {
-
- // Validate "modelType" parameter
- if (modelTypeURI == null) {
- throw new IllegalArgumentException("Model type can't be null");
- }
-
- // Initialize fields
- memoryModelTypeURI = modelTypeURI;
- this.rdfType = rdfType;
- this.resolverSession = resolverSession;
- this.statingSet = statingSet;
- this.xaResolverSession = null;
+ this(resolverSession, rdfType, modelTypeURI, statingSet, null, null);
}
-
MemoryResolver(long rdfType,
- long systemModel,
URI modelTypeURI,
Set<Stating> statingSet,
- XAResolverSession resolverSession)
+ XAResolverSession resolverSession,
+ ResolverFactory resolverFactory)
throws ResolverFactoryException {
-
+ this(resolverSession, rdfType, modelTypeURI, statingSet, resolverSession,
+ resolverFactory);
+ }
+
+ private MemoryResolver(ResolverSession resolverSession,
+ long rdfType,
+ URI modelTypeURI,
+ Set<Stating> statingSet,
+ XAResolverSession xaResolverSession,
+ ResolverFactory resolverFactory)
+ throws ResolverFactoryException {
// Validate "modelType" parameter
if (modelTypeURI == null) {
throw new IllegalArgumentException("Model type can't be null");
@@ -143,20 +142,21 @@
this.rdfType = rdfType;
this.resolverSession = resolverSession;
this.statingSet = statingSet;
- this.xaResolverSession = resolverSession;
+ this.xaResolverSession = xaResolverSession;
+
+ this.xares = (xaResolverSession != null) ?
+ new MemoryXAResource(10, xaResolverSession, resolverFactory) :
+ new DummyXAResource(10);
}
+
//
// Methods implementing Resolver
//
public XAResource getXAResource() {
- if (xaResolverSession != null) {
- return new MemoryXAResource(10, xaResolverSession);
- } else {
- return new DummyXAResource(10);
- }
+ return xares;
}
Modified: trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolverFactory.java
===================================================================
--- trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolverFactory.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryResolverFactory.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -74,11 +74,6 @@
/**
- * The preallocated local node representing the system model (<code>#</code>).
- */
- private long systemModel;
-
- /**
* The {@link Stating}s which occur in all models created by resolvers
* created by this factory.
*/
@@ -105,8 +100,6 @@
rdfType = initializer.preallocate(new URIReferenceImpl(RDF.TYPE));
initializer.preallocate(new URIReferenceImpl(modelTypeURI));
- systemModel = initializer.getSystemModel();
-
// Claim mulgara:MemoryModel
initializer.addModelType(modelTypeURI, this);
@@ -136,7 +129,6 @@
public void setDatabaseMetadata(DatabaseMetadata metadata) {
rdfType = metadata.getRdfTypeNode();
- systemModel = metadata.getSystemModelNode();
}
@@ -222,9 +214,7 @@
) throws ResolverFactoryException {
if (logger.isDebugEnabled()) logger.debug("Creating memory resolver");
return new MemoryResolver(resolverSession,
- systemResolver,
rdfType,
- systemModel,
modelTypeURI,
statingSet);
}
@@ -234,8 +224,9 @@
assert sessionFactory != null;
if (logger.isDebugEnabled()) logger.debug("Creating memory resolver factory");
try {
- return new MemoryResolver(rdfType, systemModel, modelTypeURI, statingSet,
- (XAResolverSession) sessionFactory.newWritableResolverSession());
+ return new MemoryResolver(rdfType, modelTypeURI, statingSet,
+ (XAResolverSession) sessionFactory.newWritableResolverSession(),
+ this);
} catch (ResolverSessionFactoryException er) {
throw new ResolverFactoryException("Failed to obtain a new ResolverSession", er);
}
Modified: trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryXAResource.java
===================================================================
--- trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryXAResource.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-memory/java/org/mulgara/resolver/memory/MemoryXAResource.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
- * Contributor(s): N/A.
+ * Contributor(s):
+ * Migration to AbstractXAResource copyright 2008 The Topaz Foundation
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
@@ -27,23 +28,15 @@
package org.mulgara.resolver.memory;
-// Java 2 standard packages
-import java.util.*;
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
-
-// Third party packages
-import org.apache.log4j.Logger;
-
-
+import org.mulgara.resolver.spi.AbstractXAResource;
+import org.mulgara.resolver.spi.AbstractXAResource.RMInfo;
+import org.mulgara.resolver.spi.AbstractXAResource.TxInfo;
+import org.mulgara.resolver.spi.ResolverFactory;
import org.mulgara.store.xa.SimpleXAResource;
-import org.mulgara.store.xa.SimpleXAResourceException;
import org.mulgara.store.xa.XAResolverSession;
/**
- * A dummy implementation of the {@link XAResource} interface which logs the
- * calls made to it, but otherwise ignores them.
+ * Implements the XAResource for the {@link MemoryResolver}.
*
* @created 2004-05-12
* @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a>
@@ -54,38 +47,8 @@
* Technoogies, Inc.</a>
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
-
-public class MemoryXAResource implements XAResource
-{
- /** Logger. */
- private static final Logger logger =
- Logger.getLogger(MemoryXAResource.class.getName());
-
- /**
- * Map from keyed from the {@link Integer} value of the various flags
- * defined in {@link XAResource} and mapping to the formatted name for that
- * flag.
- */
- private final static Map flagMap = new HashMap();
-
- static {
- flagMap.put(new Integer(XAResource.TMENDRSCAN), "TMENDRSCAN");
- flagMap.put(new Integer(XAResource.TMFAIL), "TMFAIL");
- flagMap.put(new Integer(XAResource.TMJOIN), "TMJOIN");
- flagMap.put(new Integer(XAResource.TMONEPHASE), "TMONEPHASE");
- flagMap.put(new Integer(XAResource.TMRESUME), "TMRESUME");
- flagMap.put(new Integer(XAResource.TMSTARTRSCAN), "TMSTARTRSCAN");
- flagMap.put(new Integer(XAResource.TMSUCCESS), "TMSUCCESS");
- flagMap.put(new Integer(XAResource.TMSUCCESS), "TMSUSPEND");
- }
-
- /** The transaction timeout value in seconds. */
- private int transactionTimeout = 0;
-
- private XAResolverSession session;
- private boolean rollback;
- private Xid xid;
-
+public class MemoryXAResource
+ extends AbstractXAResource<RMInfo<MemoryXAResource.MemoryTxInfo>,MemoryXAResource.MemoryTxInfo> {
//
// Constructor
//
@@ -94,209 +57,56 @@
* Construct a {@link MemoryXAResource} with a specified transaction timeout.
*
* @param transactionTimeout transaction timeout period, in seconds
+ * @param session the underlying resolver-session to use
+ * @param resolverFactory the resolver-factory we belong to
*/
public MemoryXAResource(int transactionTimeout,
- XAResolverSession session)
- {
- logger.debug("<init> Creating MemoryXAResource: " + this);
- this.transactionTimeout = transactionTimeout * 100;
- this.session = session;
- this.rollback = false;
+ XAResolverSession session,
+ ResolverFactory resolverFactory) {
+ super(transactionTimeout, resolverFactory, newTxInfo(session));
}
- //
- // Methods implementing XAResource
- //
-
- public void start(Xid xid, int flags) throws XAException
- {
- logger.debug("Start " + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
- switch (flags) {
- case XAResource.TMRESUME:
- logger.debug("Resuming transaction on " + System.identityHashCode(xid));
- break;
- case XAResource.TMNOFLAGS:
- try {
- session.refresh(new SimpleXAResource[] {});
- this.xid = xid;
- } catch (SimpleXAResourceException es) {
- logger.warn("Failed to refresh phases", es);
- throw new XAException(XAException.XAER_RMFAIL);
- }
- break;
- default:
- rollback = true;
- logger.error("Unrecognised flags in start: " + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
- throw new XAException(XAException.XAER_INVAL);
- }
+ protected RMInfo<MemoryTxInfo> newResourceManager() {
+ return new RMInfo<MemoryTxInfo>();
}
- public int prepare(Xid xid) throws XAException
- {
- logger.debug("Prepare " + System.identityHashCode(xid));
- logger.debug("Prepare always returns XA_OK, never XA_RDONLY");
-
- if (rollback) {
- logger.error("Attempting to prepare in failed transaction");
- throw new XAException(XAException.XA_RBROLLBACK);
- }
- if (!xid.equals(this.xid)) {
- logger.error("Attempting to prepare unknown transaction.");
- throw new XAException(XAException.XAER_NOTA);
- }
-
- try {
- session.prepare();
- } catch (SimpleXAResourceException es) {
- logger.warn("Attempt to prepare store failed", es);
- throw new XAException(XAException.XA_RBROLLBACK);
- }
-
- return XA_OK;
+ private static MemoryTxInfo newTxInfo(XAResolverSession session) {
+ MemoryTxInfo ti = new MemoryTxInfo();
+ ti.session = session;
+ return ti;
}
- public void commit(Xid xid, boolean onePhase) throws XAException
- {
- logger.debug("Commit xid=" + System.identityHashCode(xid) + " onePhase=" + onePhase);
- if (rollback) {
- logger.error("Attempting to commit in failed transaction");
- throw new XAException(XAException.XA_RBROLLBACK);
- }
- if (!xid.equals(this.xid)) {
- logger.error("Attempting to commit unknown transaction.");
- throw new XAException(XAException.XAER_NOTA);
- }
+ //
+ // Methods implementing XAResource
+ //
- try {
- if (onePhase) {
- // Check return value is XA_OK.
- prepare(xid);
- }
- } catch (Throwable th) {
- this.rollback = true;
- logger.error("Attempt to prepare in onePhaseCommit failed.", th);
- throw new XAException(XAException.XA_RBROLLBACK);
+ protected void doStart(MemoryTxInfo tx, int flags, boolean isNew) throws Exception {
+ if (flags == TMNOFLAGS || flags == TMJOIN) {
+ tx.session.refresh(new SimpleXAResource[] {});
}
-
- try {
- session.commit();
- } catch (Throwable th) {
- // This is a serious problem since the database is now in an
- // inconsistent state.
- // Make sure the exception is logged.
- logger.fatal("Failed to commit resource in transaction " + xid, th);
- throw new XAException(XAException.XAER_RMERR);
- }
}
- public void end(Xid xid, int flags) throws XAException
- {
- logger.debug("End xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+ protected void doEnd(MemoryTxInfo tx, int flags) {
}
- public void forget(Xid xid) throws XAException
- {
- logger.debug("Forget xid=" + System.identityHashCode(xid));
+ protected int doPrepare(MemoryTxInfo tx) throws Exception {
+ tx.session.prepare();
+ return XA_OK;
}
- public int getTransactionTimeout() throws XAException
- {
- logger.debug("Get transaction timeout: " + transactionTimeout);
- return transactionTimeout;
+ protected void doCommit(MemoryTxInfo tx) throws Exception {
+ tx.session.commit();
}
- public boolean isSameRM(XAResource xaResource) throws XAException
- {
- logger.debug("Is same resource manager? " + (xaResource == this) + " :: " + xaResource + " on " + this);
- return xaResource == this;
+ protected void doRollback(MemoryTxInfo tx) throws Exception {
+ tx.session.rollback();
}
- public Xid[] recover(int flag) throws XAException
- {
- logger.debug("Recover flag=" + formatFlags(flag));
- throw new XAException(XAException.XAER_RMERR);
+ protected void doForget(MemoryTxInfo tx) {
}
- public void rollback(Xid xid) throws XAException
- {
- logger.debug("Rollback " + System.identityHashCode(xid));
-
- boolean fatalError = false;
-
- if (!xid.equals(this.xid)) {
- logger.error("Attempting to rollback unknown transaction.");
- fatalError = true;
- }
-
- try {
- session.rollback();
- } catch (Throwable th) {
- // This is a serious problem since the database is now in an
- // inconsistent state.
- // Make sure the exception is logged.
- logger.fatal("Failed to rollback resource in transaction " + xid, th);
- fatalError = true;
- }
-
- if (fatalError) {
- logger.fatal("Fatal error occured while rolling back transaction " + xid + " in manager for " + this.xid);
- throw new XAException(XAException.XAER_RMERR);
- }
+ static class MemoryTxInfo extends TxInfo {
+ /** the underlying resolver-session to use */
+ public XAResolverSession session;
}
-
- public boolean setTransactionTimeout(int transactionTimeout)
- throws XAException
- {
- logger.debug("Set transaction timeout: " + transactionTimeout);
- this.transactionTimeout = transactionTimeout;
- return true;
- }
-
- //
- // Internal methods
- //
-
- /**
- * Format bitmasks defined by {@link XAResource}.
- *
- * @param flags a bitmask composed from the constants defined in
- * {@link XAResource}
- * @return a formatted representation of the <var>flags</var>
- */
- private static String formatFlags(int flags)
- {
- // Short-circuit evaluation if we've been explicitly passed no flags
- if (flags == XAResource.TMNOFLAGS) {
- return "TMNOFLAGS";
- }
-
- StringBuffer buffer = new StringBuffer();
-
- // Add any flags that are present
- for (Iterator i = flagMap.entrySet().iterator(); i.hasNext(); ) {
- Map.Entry entry = (Map.Entry)i.next();
- int entryFlag = ((Integer)entry.getKey()).intValue();
-
- // If this flag is present, add it to the formatted output and remove
- // from the bitmask
- if ((entryFlag & flags) == entryFlag) {
- if (buffer.length() > 0) {
- buffer.append(",");
- }
- buffer.append(entry.getValue());
- flags &= ~entryFlag;
- }
- }
-
- // We would expect to have removed all flags by this point
- // If there's some unknown flag we've missed, format it as hexadecimal
- if (flags != 0) {
- if (buffer.length() > 0) {
- buffer.append(",");
- }
- buffer.append("0x").append(Integer.toHexString(flags));
- }
-
- return buffer.toString();
- }
}
Copied: trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/AbstractXAResource.java (from rev 690, branches/mgr-73/src/jar/resolver-spi/java/org/mulgara/resolver/spi/AbstractXAResource.java)
===================================================================
--- trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/AbstractXAResource.java (rev 0)
+++ trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/AbstractXAResource.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2008 The Topaz Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+
+package org.mulgara.resolver.spi;
+
+// Java 2 standard packages
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import org.apache.log4j.Logger;
+
+/**
+ * A skeleton XAResource implementation. This handles the basic
+ * resource-manager and transaction management and ensures correct {@link
+ * #isSameRM} implementation. Subclasses must implement the actual
+ * functionality in the {@link #doStart}, {@link #doPrepare}, {@link
+ * #doCommit}, and {@link #doRollback} methods.
+ *
+ * @created 2008-02-16
+ * @author Ronald Tschalär
+ * @licence Apache License v2.0
+ */
+public abstract class AbstractXAResource<R extends AbstractXAResource.RMInfo<T>,T extends AbstractXAResource.TxInfo>
+ extends DummyXAResource {
+ /** Logger. */
+ private static final Logger logger =
+ Logger.getLogger(AbstractXAResource.class.getName());
+
+ protected static final Map<ResolverFactory,RMInfo<? extends TxInfo>> resourceManagers =
+ new WeakHashMap<ResolverFactory,RMInfo<? extends TxInfo>>();
+
+ protected final R resourceManager;
+ protected final T tmpTxInfo;
+
+
+ //
+ // Constructor
+ //
+
+ /**
+ * Construct an XAResource.
+ *
+ * @param transactionTimeout transaction timeout period, in seconds
+ * @param resolverFactory the resolver-factory we belong to
+ * @param txInfo the initial transaction-info
+ */
+ public AbstractXAResource(int transactionTimeout,
+ ResolverFactory resolverFactory,
+ T txInfo) {
+ super(transactionTimeout);
+
+ synchronized (resourceManagers) {
+ @SuppressWarnings("unchecked")
+ R rmgr = (R) resourceManagers.get(resolverFactory);
+ if (rmgr == null)
+ resourceManagers.put(resolverFactory, rmgr = newResourceManager());
+ this.resourceManager = rmgr;
+ }
+
+ this.tmpTxInfo = txInfo;
+ }
+
+ /**
+ * Create a new resource-manager instance - invoked only from the
+ * constructor and only when no resource-manager instance exists for the
+ * given resolver-factory.
+ */
+ protected abstract R newResourceManager();
+
+ //
+ // Methods implementing XAResource
+ //
+
+ public void start(Xid xid, int flags) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Start xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+ logger.debug("xid.format=" + xid.getFormatId() + " xid.gblTxId=" + Arrays.toString(xid.getGlobalTransactionId()) + " xid.brnchQual=" + Arrays.toString(xid.getBranchQualifier()));
+ }
+
+ T tx = resourceManager.transactions.get(new XidWrapper(xid));
+ boolean isNew = false;
+
+ switch (flags) {
+ case XAResource.TMRESUME:
+ if (tx == null) {
+ logger.error("Attempting to resume unknown transaction.");
+ throw new XAException(XAException.XAER_NOTA);
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Resuming transaction on xid=" + System.identityHashCode(xid));
+ }
+ break;
+
+ case XAResource.TMNOFLAGS:
+ if (tx != null) {
+ logger.warn("Received plain start for existing tx");
+ logger.warn("xid.format=" + xid.getFormatId() + " xid.gblTxId=" + Arrays.toString(xid.getGlobalTransactionId()) + " xid.brnchQual=" + Arrays.toString(xid.getBranchQualifier()));
+ throw new XAException(XAException.XAER_DUPID);
+ }
+ // fallthrough
+
+ case XAResource.TMJOIN:
+ if (tx == null) {
+ resourceManager.transactions.put(new XidWrapper(xid), tx = tmpTxInfo);
+ tx.xid = xid;
+ isNew = true;
+ }
+ break;
+
+ default:
+ // XXX: is this correct? Or should we actually roll back here?
+ if (tx != null)
+ tx.rollback = true;
+ logger.error("Unrecognised flags in start: xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+ throw new XAException(XAException.XAER_INVAL);
+ }
+
+ try {
+ doStart(tx, flags, isNew);
+ } catch (Throwable t) {
+ logger.warn("Failed to do start", t);
+ resourceManager.transactions.remove(new XidWrapper(xid));
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+
+ public void end(Xid xid, int flags) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("End xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+ }
+
+ T tx = resourceManager.transactions.get(new XidWrapper(xid));
+ if (tx == null) {
+ logger.error("Attempting to end unknown transaction.");
+ throw new XAException(XAException.XAER_NOTA);
+ }
+
+ try {
+ doEnd(tx, flags);
+ } catch (Throwable t) {
+ logger.warn("Failed to do end", t);
+ resourceManager.transactions.remove(new XidWrapper(xid));
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+
+ public int prepare(Xid xid) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Prepare xid=" + System.identityHashCode(xid));
+ }
+
+ T tx = resourceManager.transactions.get(new XidWrapper(xid));
+ if (tx == null) {
+ logger.error("Attempting to prepare unknown transaction.");
+ throw new XAException(XAException.XAER_NOTA);
+ }
+
+ if (tx.rollback) {
+ logger.info("Attempting to prepare in failed transaction");
+ rollback(xid);
+ throw new XAException(XAException.XA_RBROLLBACK);
+ }
+
+ try {
+ int sts = doPrepare(tx);
+ if (sts == XA_RDONLY)
+ resourceManager.transactions.remove(new XidWrapper(xid));
+ return sts;
+ } catch (Throwable t) {
+ logger.warn("Attempt to prepare failed", t);
+ rollback(xid);
+ throw new XAException(XAException.XA_RBROLLBACK);
+ }
+ }
+
+ public void commit(Xid xid, boolean onePhase) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Commit xid=" + System.identityHashCode(xid) + " onePhase=" + onePhase);
+ }
+
+ T tx = resourceManager.transactions.get(new XidWrapper(xid));
+ if (tx == null) {
+ logger.error("Attempting to commit unknown transaction.");
+ throw new XAException(XAException.XAER_NOTA);
+ }
+ if (tx.rollback) {
+ logger.error("Attempting to commit in failed transaction");
+ rollback(xid);
+ throw new XAException(XAException.XA_RBROLLBACK);
+ }
+
+ try {
+ if (onePhase) {
+ int sts = doPrepare(tx);
+ if (sts == XA_RDONLY) {
+ resourceManager.transactions.remove(new XidWrapper(xid));
+ return;
+ }
+ }
+ } catch (Throwable th) {
+ logger.error("Attempt to prepare in onePhaseCommit failed.", th);
+ rollback(xid);
+ throw new XAException(XAException.XA_RBROLLBACK);
+ }
+
+ boolean clean = true;
+ try {
+ doCommit(tx);
+ } catch (XAException xae) {
+ if (isHeuristic(xae)) {
+ clean = false;
+ }
+ throw xae;
+ } catch (Throwable th) {
+ // This is a serious problem since the database is now in an
+ // inconsistent state.
+ // Make sure the exception is logged.
+ logger.fatal("Failed to commit resource in transaction " + xid, th);
+ throw new XAException(XAException.XAER_RMERR);
+ } finally {
+ if (clean) {
+ resourceManager.transactions.remove(new XidWrapper(xid));
+ }
+ }
+ }
+
+ public void rollback(Xid xid) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Rollback xid=" + System.identityHashCode(xid));
+ }
+
+ T tx = resourceManager.transactions.get(new XidWrapper(xid));
+ if (tx == null) {
+ logger.error("Attempting to rollback unknown transaction.");
+ throw new XAException(XAException.XAER_NOTA);
+ }
+
+ boolean clean = true;
+ try {
+ doRollback(tx);
+ } catch (XAException xae) {
+ if (isHeuristic(xae)) {
+ clean = false;
+ }
+ throw xae;
+ } catch (Throwable th) {
+ // This is a serious problem since the database is now in an
+ // inconsistent state.
+ // Make sure the exception is logged.
+ logger.fatal("Failed to rollback resource in transaction " + xid, th);
+ throw new XAException(XAException.XAER_RMERR);
+ } finally {
+ if (clean) {
+ resourceManager.transactions.remove(new XidWrapper(xid));
+ }
+ }
+ }
+
+ public void forget(Xid xid) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Forget xid=" + System.identityHashCode(xid));
+ }
+
+ T tx = resourceManager.transactions.get(new XidWrapper(xid));
+ if (tx == null) {
+ logger.error("Attempting to forget unknown transaction.");
+ throw new XAException(XAException.XAER_NOTA);
+ }
+
+ boolean clean = true;
+ try {
+ doForget(tx);
+ } catch (XAException xae) {
+ if (xae.errorCode == XAException.XAER_RMERR) {
+ clean = false;
+ }
+ throw xae;
+ } catch (Throwable th) {
+ logger.error("Failed to forget transaction " + xid, th);
+ clean = false;
+ throw new XAException(XAException.XAER_RMERR);
+ } finally {
+ if (clean) {
+ resourceManager.transactions.remove(new XidWrapper(xid));
+ }
+ }
+ }
+
+ public Xid[] recover(int flag) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Recover flag=" + formatFlags(flag));
+ }
+
+ throw new XAException(XAException.XAER_RMERR);
+ }
+
+ public boolean isSameRM(XAResource xaResource) throws XAException {
+ boolean same = (xaResource instanceof AbstractXAResource) &&
+ ((AbstractXAResource)xaResource).resourceManager == resourceManager;
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Is same resource manager? " + same + " :: " + xaResource + " on " + this);
+ }
+
+ return same;
+ }
+
+ /**
+ * Invoked on start with valid flags and tx state.
+ *
+ * @param tx the transaction being started; always non-null
+ * @param flags one of TMNOFLAGS, TMRESUME, or TMJOIN
+ * @param isNew true if <var>tx</var> was created as part of this start()
+ * @throws Exception
+ */
+ protected abstract void doStart(T tx, int flags, boolean isNew) throws Exception;
+
+ /**
+ * Invoked on end().
+ *
+ * @param tx the transaction being ended; always non-null
+ * @param flags one of TMSUCCESS, TMFAIL, or TMSUSPEND
+ * @throws Exception
+ */
+ protected abstract void doEnd(T tx, int flags) throws Exception;
+
+ /**
+ * Invoked on prepare() or commit(onePhase=true).
+ *
+ * @param tx the transaction being prepared; always non-null
+ * @return XA_OK or XA_RDONLY
+ * @throws Exception
+ */
+ protected abstract int doPrepare(T tx) throws Exception;
+
+ /**
+ * Invoked on commit().
+ *
+ * @param tx the transaction being committed; always non-null
+ * @throws Exception
+ */
+ protected abstract void doCommit(T tx) throws Exception;
+
+ /**
+ * Invoked on (explicit or implicit) rollback().
+ *
+ * @param tx the transaction being rolled back; always non-null
+ * @throws Exception
+ */
+ protected abstract void doRollback(T tx) throws Exception;
+
+ /**
+ * Invoked on forget().
+ *
+ * @param tx the transaction to forget; always non-null
+ * @throws Exception
+ */
+ protected abstract void doForget(T tx) throws Exception;
+
+ private static boolean isHeuristic(XAException xae) {
+ return (xae.errorCode == XAException.XA_HEURHAZ || xae.errorCode == XAException.XA_HEURCOM ||
+ xae.errorCode == XAException.XA_HEURRB || xae.errorCode == XAException.XA_HEURMIX);
+ }
+
+
+ /** The resource-manager info */
+ public static class RMInfo<T extends TxInfo> {
+ /** the list of active transactions */
+ public final Map<XidWrapper,T> transactions =
+ Collections.synchronizedMap(new HashMap<XidWrapper,T>());
+ }
+
+ /** The info pertaining to a single transaction */
+ public static class TxInfo {
+ /** the underlying Xid of this transaction; not valid till the first start() */
+ public Xid xid;
+ /** true if this transaction has been marked for rollback */
+ public boolean rollback;
+ }
+
+ /**
+ * Xid-wrapper that implements hashCode() and equals(). JTA does not require
+ * Xid's to implement hashCode() and equals(), so in order to be able to use
+ * them as keys in a map we need to wrap them with something that implements
+ * them based on the individual fields of the Xid.
+ */
+ public static class XidWrapper {
+ private final Xid xid;
+ private final int hash;
+
+ public XidWrapper(Xid xid) {
+ this.xid = xid;
+ this.hash = Arrays.hashCode(xid.getBranchQualifier());
+ }
+
+ public int hashCode() {
+ return hash;
+ }
+
+ public boolean equals(Object other) {
+ Xid o;
+
+ if (other instanceof XidWrapper) {
+ o = ((XidWrapper)other).xid;
+ } else if (other instanceof Xid) {
+ o = (Xid)other;
+ } else {
+ return false;
+ }
+
+ if (o == xid)
+ return true;
+ return o.getFormatId() == xid.getFormatId() &&
+ Arrays.equals(o.getGlobalTransactionId(), xid.getGlobalTransactionId()) &&
+ Arrays.equals(o. getBranchQualifier(), xid. getBranchQualifier());
+ }
+ }
+}
Modified: trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/DummyXAResource.java
===================================================================
--- trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/DummyXAResource.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/DummyXAResource.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -54,15 +54,14 @@
public class DummyXAResource implements XAResource
{
/** Logger. */
- private static final Logger logger =
- Logger.getLogger(DummyXAResource.class.getName());
+ private static final Logger logger = Logger.getLogger(DummyXAResource.class.getName());
/**
* Map from keyed from the {@link Integer} value of the various flags
* defined in {@link XAResource} and mapping to the formatted name for that
* flag.
*/
- private final static Map flagMap = new HashMap();
+ protected final static Map<Integer,String> flagMap = new HashMap<Integer,String>();
static {
flagMap.put(new Integer(XAResource.TMENDRSCAN), "TMENDRSCAN");
@@ -76,20 +75,28 @@
}
/** The transaction timeout value in seconds. */
- private int transactionTimeout = 0;
+ protected int transactionTimeout = 0;
//
// Constructor
//
/**
+ * Construct a {@link DummyXAResource} with a default 10 second transaction timeout.
+ */
+ public DummyXAResource() {
+ this(10);
+ }
+
+ /**
* Construct a {@link DummyXAResource} with a specified transaction timeout.
*
* @param transactionTimeout transaction timeout period, in seconds
*/
- public DummyXAResource(int transactionTimeout)
- {
- logger.debug("Creating DummyXAResource with timeout " + transactionTimeout);
+ public DummyXAResource(int transactionTimeout) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Creating " + getClass().getName() + " with timeout " + transactionTimeout);
+ }
this.transactionTimeout = transactionTimeout;
}
@@ -97,61 +104,70 @@
// Methods implementing XAResource
//
- public void commit(Xid xid, boolean onePhase) throws XAException
- {
- logger.debug("Commit xid=" + System.identityHashCode(xid) + " onePhase=" + onePhase);
+ public void commit(Xid xid, boolean onePhase) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Commit xid=" + System.identityHashCode(xid) + " onePhase=" + onePhase);
+ }
}
- public void end(Xid xid, int flags) throws XAException
- {
- logger.debug("End xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+ public void end(Xid xid, int flags) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("End xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+ }
}
- public void forget(Xid xid) throws XAException
- {
- logger.debug("Forget xid=" + System.identityHashCode(xid));
+ public void forget(Xid xid) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Forget xid=" + System.identityHashCode(xid));
+ }
}
- public int getTransactionTimeout() throws XAException
- {
- logger.debug("Get transaction timeout: " + transactionTimeout);
+ public int getTransactionTimeout() throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Get transaction timeout: " + transactionTimeout);
+ }
return transactionTimeout;
}
- public boolean isSameRM(XAResource xaResource) throws XAException
- {
- logger.debug("Is same resource manager? " + (xaResource == this));
+ public boolean isSameRM(XAResource xaResource) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Is same resource manager? " + (xaResource == this));
+ }
return xaResource == this;
}
- public int prepare(Xid xid) throws XAException
- {
- logger.debug("Prepare " + System.identityHashCode(xid));
+ public int prepare(Xid xid) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Prepare " + System.identityHashCode(xid));
+ }
return XA_OK;
}
- public Xid[] recover(int flag) throws XAException
- {
- logger.debug("Recover flag=" + formatFlags(flag));
+ public Xid[] recover(int flag) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Recover flag=" + formatFlags(flag));
+ }
throw new XAException(XAException.XAER_RMERR);
}
- public void rollback(Xid xid) throws XAException
- {
- logger.debug("Rollback " + System.identityHashCode(xid));
+ public void rollback(Xid xid) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Rollback " + System.identityHashCode(xid));
+ }
}
- public boolean setTransactionTimeout(int transactionTimeout)
- throws XAException
- {
- logger.debug("Set transaction timeout: " + transactionTimeout);
+ public boolean setTransactionTimeout(int transactionTimeout) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Set transaction timeout: " + transactionTimeout);
+ }
this.transactionTimeout = transactionTimeout;
return true;
}
- public void start(Xid xid, int flags) throws XAException
- {
- logger.debug("Start " + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+ public void start(Xid xid, int flags) throws XAException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Start " + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
+ }
}
//
@@ -161,23 +177,20 @@
/**
* Format bitmasks defined by {@link XAResource}.
*
- * @param flags a bitmask composed from the constants defined in
- * {@link XAResource}
+ * @param flags a bitmask composed from the constants defined in {@link XAResource}
* @return a formatted representation of the <var>flags</var>
*/
- private static String formatFlags(int flags)
- {
+ protected static final String formatFlags(int flags) {
// Short-circuit evaluation if we've been explicitly passed no flags
if (flags == XAResource.TMNOFLAGS) {
return "TMNOFLAGS";
}
- StringBuffer buffer = new StringBuffer();
+ StringBuilder buffer = new StringBuilder();
// Add any flags that are present
- for (Iterator i = flagMap.entrySet().iterator(); i.hasNext(); ) {
- Map.Entry entry = (Map.Entry)i.next();
- int entryFlag = ((Integer)entry.getKey()).intValue();
+ for (Map.Entry<Integer,String> entry : flagMap.entrySet()) {
+ int entryFlag = entry.getKey().intValue();
// If this flag is present, add it to the formatted output and remove
// from the bitmask
Modified: trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/TripleSetWrapperStatements.java
===================================================================
--- trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/TripleSetWrapperStatements.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-spi/java/org/mulgara/resolver/spi/TripleSetWrapperStatements.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -71,8 +71,8 @@
TObjectLongHashMap nodeMap;
- Set<Triple> triples;
- Iterator<Triple> iter;
+ Set<? extends Triple> triples;
+ Iterator<? extends Triple> iter;
Triple currentTriple;
boolean persistent;
@@ -80,7 +80,7 @@
// Constructors
//
- public TripleSetWrapperStatements(Set<Triple> triples, ResolverSession resolverSession, int persistent)
+ public TripleSetWrapperStatements(Set<? extends Triple> triples, ResolverSession resolverSession, int persistent)
throws TuplesException {
this.triples = triples;
this.resolverSession = resolverSession;
Modified: trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolver.java
===================================================================
--- trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolver.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolver.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,9 @@
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
- * Contributor(s): N/A.
+ * Contributor(s):
+ * Move to associate XAResource with Resolver Factory
+ * copyright 2008 The Topaz Foundation
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
@@ -131,7 +133,8 @@
long systemModel,
URI modelTypeURI,
XAResolverSession resolverSession,
- XAStatementStore statementStore)
+ XAStatementStore statementStore,
+ ResolverFactory resolverFactory)
throws IllegalArgumentException, ResolverFactoryException
{
// Validate parameters
@@ -153,7 +156,8 @@
this.xaresource = new StatementStoreXAResource(
10, // transaction timeout in seconds
resolverSession,
- new SimpleXAResource[] { statementStore });
+ new SimpleXAResource[] { statementStore },
+ resolverFactory);
}
StatementStoreResolver(Resolver systemResolver,
@@ -161,7 +165,8 @@
long systemModel,
URI modelTypeURI,
XAResolverSession resolverSession,
- XAStatementStore statementStore)
+ XAStatementStore statementStore,
+ ResolverFactory resolverFactory)
throws IllegalArgumentException, ResolverFactoryException
{
// Validate parameters
@@ -182,7 +187,8 @@
this.xaresource = new StatementStoreXAResource(
10, // transaction timeout in seconds
resolverSession,
- new SimpleXAResource[] { statementStore });
+ new SimpleXAResource[] { statementStore },
+ resolverFactory);
}
@@ -374,7 +380,7 @@
+ resolverSession.globalize(statements.getObject()) + " "
+ resolverSession.globalize(model) + "]", e);
} catch (Exception eg) {
- throw new ResolverException("Failed to globalize in debug", eg);
+ throw new ResolverException("Failed to globalize in debug", eg);
}
throw new ResolverException("Couldn't make statement " + occurs + " in " + model, e);
} catch (TuplesException e) {
Modified: trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolverFactory.java
===================================================================
--- trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolverFactory.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreResolverFactory.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -195,7 +195,8 @@
: (XAResolverSession) resolverSessionFactory.newReadOnlyResolverSession(),
allowWrites
? statementStore.newWritableStatementStore()
- : statementStore.newReadOnlyStatementStore());
+ : statementStore.newReadOnlyStatementStore(),
+ this);
} catch (ResolverSessionFactoryException er) {
throw new ResolverFactoryException(
"Failed to obtain a new ResolverSession", er);
@@ -216,7 +217,8 @@
:
(XAResolverSession) resolverSessionFactory.newReadOnlyResolverSession(),
allowWrites ? statementStore.newWritableStatementStore()
- : statementStore.newReadOnlyStatementStore());
+ : statementStore.newReadOnlyStatementStore(),
+ this);
} catch (ResolverSessionFactoryException er) {
throw new ResolverFactoryException(
"Failed to obtain a new ResolverSession", er);
Modified: trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreXAResource.java
===================================================================
--- trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreXAResource.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-store/java/org/mulgara/resolver/store/StatementStoreXAResource.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -17,6 +17,7 @@
* Plugged In Software Pty Ltd. All Rights Reserved.
*
* Contributor(s): N/A.
+ * Migration to AbstractXAResource copyright 2008 The Topaz Foundation
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
@@ -28,22 +29,22 @@
package org.mulgara.resolver.store;
// Java 2 standard packages
-import java.util.*;
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.XAResource;
-import javax.transaction.xa.Xid;
+import java.util.HashSet;
+import java.util.Set;
// Third party packages
import org.apache.log4j.Logger;
-
+import org.mulgara.resolver.spi.AbstractXAResource;
+import org.mulgara.resolver.spi.AbstractXAResource.RMInfo;
+import org.mulgara.resolver.spi.AbstractXAResource.TxInfo;
+import org.mulgara.resolver.spi.ResolverFactory;
import org.mulgara.store.xa.SimpleXAResource;
import org.mulgara.store.xa.SimpleXAResourceException;
import org.mulgara.store.xa.XAResolverSession;
/**
- * A dummy implementation of the {@link XAResource} interface which logs the
- * calls made to it, but otherwise ignores them.
+ * Implements the XAResource for the {@link StatementStoreResolver}.
*
* @created 2004-05-12
* @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a>
@@ -55,40 +56,15 @@
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
-public class StatementStoreXAResource implements XAResource
-{
+public class StatementStoreXAResource
+ extends AbstractXAResource<RMInfo<StatementStoreXAResource.StatementStoreTxInfo>, StatementStoreXAResource.StatementStoreTxInfo> {
/** Logger. */
private static final Logger logger =
Logger.getLogger(StatementStoreXAResource.class.getName());
- /**
- * Map from keyed from the {@link Integer} value of the various flags
- * defined in {@link XAResource} and mapping to the formatted name for that
- * flag.
- */
- private final static Map flagMap = new HashMap();
-
- static {
- flagMap.put(new Integer(XAResource.TMENDRSCAN), "TMENDRSCAN");
- flagMap.put(new Integer(XAResource.TMFAIL), "TMFAIL");
- flagMap.put(new Integer(XAResource.TMJOIN), "TMJOIN");
- flagMap.put(new Integer(XAResource.TMONEPHASE), "TMONEPHASE");
- flagMap.put(new Integer(XAResource.TMRESUME), "TMRESUME");
- flagMap.put(new Integer(XAResource.TMSTARTRSCAN), "TMSTARTRSCAN");
- flagMap.put(new Integer(XAResource.TMSUCCESS), "TMSUCCESS");
- flagMap.put(new Integer(XAResource.TMSUSPEND), "TMSUSPEND");
- }
-
- /** The transaction timeout value in seconds. */
- private int transactionTimeout = 0;
-
- private SimpleXAResource[] resources;
- private XAResolverSession session;
- private boolean rollback;
- private Xid xid;
// Used to prevent multiple calls to prepare on the store layer.
// Set of session's that have been prepared.
- private static Set preparing = new HashSet();
+ public static Set<XAResolverSession> preparing = new HashSet<XAResolverSession>();
//
// Constructor
@@ -98,292 +74,127 @@
* Construct a {@link StatementStoreXAResource} with a specified transaction timeout.
*
* @param transactionTimeout transaction timeout period, in seconds
+ * @param session the underlying resolver-session to use
+ * @param resources
+ * @param resolverFactory the resolver-factory we belong to
*/
public StatementStoreXAResource(int transactionTimeout,
XAResolverSession session,
- SimpleXAResource[] resources)
- {
- if (logger.isDebugEnabled()) {
- logger.debug("<init> Creating StatementStoreXAResource: " + this);
- }
- this.transactionTimeout = transactionTimeout * 100;
- this.resources = resources;
- this.session = session;
+ SimpleXAResource[] resources,
+ ResolverFactory resolverFactory) {
+ super(transactionTimeout, resolverFactory, newTxInfo(session, resources));
}
+ protected RMInfo<StatementStoreTxInfo> newResourceManager() {
+ return new RMInfo<StatementStoreTxInfo>();
+ }
+
+ private static StatementStoreTxInfo newTxInfo(XAResolverSession session,
+ SimpleXAResource[] resources) {
+ StatementStoreTxInfo ti = new StatementStoreTxInfo();
+ ti.session = session;
+ ti.resources = resources;
+ return ti;
+ }
+
//
// Methods implementing XAResource
//
- public void start(Xid xid, int flags) throws XAException
- {
- if (logger.isDebugEnabled()) {
- logger.debug("Start " + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
- }
- switch (flags) {
- case XAResource.TMNOFLAGS:
- try {
- session.refresh(resources);
- this.xid = xid;
- this.rollback = false;
- } catch (SimpleXAResourceException es) {
- logger.error("Failed to obtain phases", es);
- throw new XAException(XAException.XAER_RMFAIL);
- }
- break;
- case XAResource.TMRESUME:
- if (!xid.equals(this.xid)) {
- logger.error("Attempt to resume resource in wrong transaction.");
- throw new XAException(XAException.XAER_INVAL);
- }
- break;
- case XAResource.TMJOIN:
- if (!xid.equals(this.xid)) {
- logger.error("Attempt to join with wrong transaction.");
- throw new XAException(XAException.XAER_INVAL);
- }
- break;
- default: // Currently fall-through.
- rollback = true;
- logger.warn("Unrecognised flags in start: " + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
- if (logger.isDebugEnabled()) {
- logger.debug("This XAResource = " + System.identityHashCode(this.xid));
- }
- throw new XAException(XAException.XAER_INVAL);
+ protected void doStart(StatementStoreTxInfo tx, int flags, boolean isNew) throws Exception {
+ if (flags == TMNOFLAGS) {
+ tx.session.refresh(tx.resources);
}
}
- public int prepare(Xid xid) throws XAException
- {
- if (logger.isDebugEnabled()) {
- logger.debug("XAResource " + this + " Prepare " + System.identityHashCode(xid) + " With Session: " + System.identityHashCode(session));
- }
+ protected void doEnd(StatementStoreTxInfo tx, int flags) {
+ }
- if (rollback) {
- logger.error("Attempting to prepare in failed transaction");
- throw new XAException(XAException.XA_RBROLLBACK);
- }
- if (!xid.equals(this.xid)) {
- logger.error("Attempting to prepare unknown transaction.");
- throw new XAException(XAException.XAER_NOTA);
- }
- synchronized(preparing) {
- if (preparing.contains(session)) {
+ protected int doPrepare(StatementStoreTxInfo tx) throws Exception {
+ synchronized (preparing) {
+ if (preparing.contains(tx.session)) {
return XA_OK;
} else {
- preparing.add(session);
+ preparing.add(tx.session);
}
}
try {
- session.prepare();
+ tx.session.prepare();
} catch (SimpleXAResourceException es) {
- logger.warn("Attempt to prepare store failed", es);
- synchronized(preparing) {
- preparing.remove(session);
+ synchronized (preparing) {
+ preparing.remove(tx.session);
}
- throw new XAException(XAException.XA_RBROLLBACK);
+ throw es;
}
return XA_OK;
}
- public void commit(Xid xid, boolean onePhase) throws XAException
- {
+ protected void doCommit(StatementStoreTxInfo tx) throws Exception {
try {
- if (logger.isDebugEnabled()) {
- logger.debug("XAResource " + this + " Commit xid=" + System.identityHashCode(xid) + " onePhase=" + onePhase + " session=" + System.identityHashCode(session));
- }
- if (rollback) {
- logger.error("Attempting to commit in failed transaction");
- throw new XAException(XAException.XA_RBROLLBACK);
- }
- if (!xid.equals(this.xid)) {
- logger.error("Attempting to commit unknown transaction.");
- throw new XAException(XAException.XAER_NOTA);
- }
- try {
- if (onePhase) {
- // Currently prepare only returns XA_OK, and throws an exception on failure.
- prepare(xid);
- }
- } catch (Throwable th) {
- this.rollback = true;
- logger.error("Attempt to prepare in onePhaseCommit failed.", th);
- throw new XAException(XAException.XA_RBROLLBACK);
- }
-
- try {
- session.commit();
- } catch (Throwable th) {
- // This is a serious problem since the database is now in an
- // inconsistent state.
- // Make sure the exception is logged.
- logger.fatal("Failed to commit resource in transaction " + xid, th);
- throw new XAException(XAException.XAER_RMERR);
- }
+ tx.session.commit();
} finally {
- cleanup("commit");
+ cleanup("commit", tx);
}
-
}
- public void end(Xid xid, int flags) throws XAException
- {
- if (logger.isDebugEnabled()) {
- logger.debug("End xid=" + System.identityHashCode(xid) + " flags=" + formatFlags(flags));
- }
- }
-
- public void forget(Xid xid) throws XAException
- {
- if (logger.isDebugEnabled()) {
- logger.debug("Forget xid=" + System.identityHashCode(xid));
- }
+ protected void doForget(StatementStoreTxInfo tx) throws Exception {
try {
- synchronized(preparing) {
- if (preparing.contains(session)) {
- rollback(xid);
+ synchronized (preparing) {
+ if (preparing.contains(tx.session)) {
+ doRollback(tx);
}
}
} finally {
- cleanup("forget");
+ cleanup("forget", tx);
}
}
- public int getTransactionTimeout() throws XAException
- {
- if (logger.isDebugEnabled()) {
- logger.debug("Get transaction timeout: " + transactionTimeout);
- }
- return transactionTimeout;
- }
-
- public boolean isSameRM(XAResource xaResource) throws XAException
- {
- return xaResource == this;
- }
-
- public Xid[] recover(int flag) throws XAException
- {
- if (logger.isDebugEnabled()) {
- logger.debug("Recover flag=" + formatFlags(flag));
- }
- throw new XAException(XAException.XAER_RMERR);
- }
-
- public void rollback(Xid xid) throws XAException
- {
- if (logger.isDebugEnabled()) {
- logger.debug("Rollback " + System.identityHashCode(xid));
- }
-
- boolean fatalError = false;
-
- if (!xid.equals(this.xid)) {
- logger.error("Attempting to rollback unknown transaction.");
- fatalError = true;
- }
-
+ protected void doRollback(StatementStoreTxInfo tx) throws Exception {
try {
- if (logger.isDebugEnabled()) {
- logger.debug("Rolling back phase");
- }
- session.rollback();
- } catch (Throwable th) {
- // This is a serious problem since the database is now in an
- // inconsistent state.
- // Make sure the exception is logged.
- logger.fatal("Failed to rollback resource in transaction " + xid, th);
- fatalError = true;
+ tx.session.rollback();
} finally {
- cleanup("rollback");
+ cleanup("rollback", tx);
}
-
- if (fatalError) {
- logger.fatal("Fatal error occured while rolling back transaction " + xid + " in manager for " + this.xid);
- throw new XAException(XAException.XAER_RMERR);
- }
}
- public boolean setTransactionTimeout(int transactionTimeout)
- throws XAException
- {
- if (logger.isDebugEnabled()) {
- logger.debug("Set transaction timeout: " + transactionTimeout);
- }
- this.transactionTimeout = transactionTimeout;
- return true;
- }
-
//
// Internal methods
//
- /**
- * Format bitmasks defined by {@link XAResource}.
- *
- * @param flags a bitmask composed from the constants defined in
- * {@link XAResource}
- * @return a formatted representation of the <var>flags</var>
- */
- private static String formatFlags(int flags)
- {
- // Short-circuit evaluation if we've been explicitly passed no flags
- if (flags == XAResource.TMNOFLAGS) {
- return "TMNOFLAGS";
+ private void cleanup(String operation, StatementStoreTxInfo tx) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Performing cleanup from " + operation);
}
-
- StringBuffer buffer = new StringBuffer();
-
- // Add any flags that are present
- for (Iterator i = flagMap.entrySet().iterator(); i.hasNext(); ) {
- Map.Entry entry = (Map.Entry)i.next();
- int entryFlag = ((Integer)entry.getKey()).intValue();
-
- // If this flag is present, add it to the formatted output and remove
- // from the bitmask
- if ((entryFlag & flags) == entryFlag) {
- if (buffer.length() > 0) {
- buffer.append(",");
- }
- buffer.append(entry.getValue());
- flags &= ~entryFlag;
- }
- }
-
- // We would expect to have removed all flags by this point
- // If there's some unknown flag we've missed, format it as hexadecimal
- if (flags != 0) {
- if (buffer.length() > 0) {
- buffer.append(",");
- }
- buffer.append("0x").append(Integer.toHexString(flags));
- }
-
- return buffer.toString();
- }
-
-
- private void cleanup(String operation) {
try {
- synchronized(preparing) {
- if (preparing.contains(session)) {
- preparing.remove(session);
+ synchronized (preparing) {
+ if (preparing.contains(tx.session)) {
+ preparing.remove(tx.session);
} else {
- logger.debug("Already committed/rolledback in this transaction");
+ if (logger.isDebugEnabled()) {
+ logger.debug("Already committed/rolledback in this transaction");
+ }
}
}
} finally {
try {
if (logger.isDebugEnabled()) {
- logger.debug("Releasing session after " + operation + " " + session);
+ logger.debug("Releasing session after " + operation + " " + tx.session);
}
- session.release();
- session = null;
+ tx.session.release();
+ tx.session = null;
} catch (SimpleXAResourceException es) {
logger.error("Attempt to release store failed", es);
}
}
}
+
+ static class StatementStoreTxInfo extends TxInfo {
+ /** the underlying resolver-session to use */
+ public XAResolverSession session;
+
+ /** the underlying resources */
+ public SimpleXAResource[] resources;
+ }
}
Modified: trunk/src/jar/resolver-test/java/org/mulgara/resolver/test/TestResolverUnitTest.java
===================================================================
--- trunk/src/jar/resolver-test/java/org/mulgara/resolver/test/TestResolverUnitTest.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-test/java/org/mulgara/resolver/test/TestResolverUnitTest.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -131,7 +131,7 @@
Session session = database.newSession();
Variable[] varArray = new Variable[] { new Variable("a"), new Variable("b") };
- List<Object> variables = Arrays.asList((Object[])varArray);
+ List<Variable> variables = Arrays.asList((Variable[])varArray);
Answer answer = session.query(new Query(
variables, // SELECT
@@ -178,7 +178,7 @@
Session session = database.newSession();
Variable[] varArray = new Variable[] { new Variable("a"), new Variable("c") };
- List<Object> variables = Arrays.asList((Object[])varArray);
+ List<Variable> variables = Arrays.asList((Variable[])varArray);
Answer answer = session.query(new Query(
variables, // SELECT
@@ -223,7 +223,7 @@
Session session = database.newSession();
Variable[] varArray = new Variable[] { new Variable("a"), new Variable("b"), new Variable("c") };
- List<Object> variables = Arrays.asList((Object[])varArray);
+ List<Variable> variables = Arrays.asList((Variable[])varArray);
Answer answer = session.query(new Query(
variables, // SELECT
@@ -274,7 +274,7 @@
Session session = database.newSession();
Variable[] varArray = new Variable[] { new Variable("a"), new Variable("b"), new Variable("c") };
- List<Object> variables = Arrays.asList((Object[])varArray);
+ List<Variable> variables = Arrays.asList(varArray);
Answer answer = session.query(new Query(
variables, // SELECT
@@ -325,7 +325,7 @@
Session session = database.newSession();
Variable[] varArray = new Variable[] { new Variable("a"), new Variable("b"), new Variable("c") };
- List<Object> variables = Arrays.asList((Object[])varArray);
+ List<Variable> variables = Arrays.asList(varArray);
try {
session.query(new Query(
Modified: trunk/src/jar/resolver-view/java/org/mulgara/resolver/view/ViewResolverUnitTest.java
===================================================================
--- trunk/src/jar/resolver-view/java/org/mulgara/resolver/view/ViewResolverUnitTest.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/resolver-view/java/org/mulgara/resolver/view/ViewResolverUnitTest.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -607,7 +607,7 @@
// Evaluate the query
Answer answer = new ArrayAnswer(session.query(new Query(
- (List<Object>)(List)test.selectList, // SELECT
+ test.selectList, // SELECT
test.model, // FROM
test.query, // WHERE
null, // HAVING
Modified: trunk/src/jar/server-beep/java/org/mulgara/server/beep/BEEPSession.java
===================================================================
--- trunk/src/jar/server-beep/java/org/mulgara/server/beep/BEEPSession.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/server-beep/java/org/mulgara/server/beep/BEEPSession.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
- * Contributor(s): N/A.
+ * Contributor(s):
+ * XAResource addition copyright 2008 The Topaz Foundation
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
@@ -32,6 +33,7 @@
import java.net.*;
import java.rmi.RemoteException;
import java.util.*;
+import javax.transaction.xa.XAResource;
// Third party packages
import org.apache.log4j.Logger; // Apache Log4J
@@ -537,4 +539,11 @@
throw new UnsupportedOperationException("This operation is only supported on local sessions.");
}
+ public XAResource getXAResource() throws QueryException {
+ throw new QueryException("External transactions not implemented under Beep");
+ }
+
+ public XAResource getReadOnlyXAResource() throws QueryException {
+ throw new QueryException("External transactions not implemented under Beep");
+ }
}
Modified: trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSession.java
===================================================================
--- trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSession.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSession.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
- * Contributor(s): N/A.
+ * Contributor(s):
+ * XAResource access copyright 2007 The Topaz Foundation.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
@@ -308,4 +309,9 @@
public void login(URI securityDomain, String username,
char[] password) throws RemoteException;
+ /**
+ * Obtain an XAResource for this session.
+ */
+ public RemoteXAResource getXAResource() throws QueryException, RemoteException;
+ public RemoteXAResource getReadOnlyXAResource() throws QueryException, RemoteException;
}
Modified: trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSessionWrapperSession.java
===================================================================
--- trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSessionWrapperSession.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteSessionWrapperSession.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
- * Contributor(s): N/A.
+ * Contributor(s):
+ * XAResource access copyright 2007 The Topaz Foundation.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
@@ -34,6 +35,7 @@
import java.rmi.RemoteException;
import java.util.*;
import java.io.*;
+import javax.transaction.xa.XAResource;
// Third party packages
import org.apache.log4j.Logger;
@@ -210,7 +212,7 @@
/**
* {@inheritDoc}
*/
- public void insert(URI modelURI, Set<Triple> statements) throws QueryException {
+ public void insert(URI modelURI, Set<? extends Triple> statements) throws QueryException {
try {
remoteSession.insert(modelURI, statements);
@@ -240,7 +242,7 @@
/**
* {@inheritDoc}
*/
- public void delete(URI modelURI, Set<Triple> statements) throws QueryException {
+ public void delete(URI modelURI, Set<? extends Triple> statements) throws QueryException {
try {
remoteSession.delete(modelURI, statements);
@@ -610,4 +612,19 @@
}
}
+ public XAResource getXAResource() throws QueryException {
+ try {
+ return new RemoteXAResourceWrapperXAResource(remoteSession.getXAResource());
+ } catch (RemoteException re){
+ throw new QueryException("Java RMI failure", re);
+ }
+ }
+
+ public XAResource getReadOnlyXAResource() throws QueryException {
+ try {
+ return new RemoteXAResourceWrapperXAResource(remoteSession.getReadOnlyXAResource());
+ } catch (RemoteException re){
+ throw new QueryException("Java RMI failure", re);
+ }
+ }
}
Copied: trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResource.java (from rev 690, branches/mgr-73/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResource.java)
===================================================================
--- trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResource.java (rev 0)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResource.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2007 The Topaz Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+package org.mulgara.server.rmi;
+
+// Java 2 standard packages
+import java.io.Serializable;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import org.apache.log4j.Category;
+
+// Local packages
+
+/**
+ * Remote XAResource.
+ *
+ */
+/**
+ * An analogue to XAResource that is suitable for implementing as an RMI object.
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ * @created 2007-11-28
+ * @licence Apache License v2.0
+ */
+public interface RemoteXAResource extends Remote
+{
+ public void commit(Xid xid, boolean onePhase) throws XAException, RemoteException;
+
+ public void end(Xid xid, int flags) throws XAException, RemoteException;
+
+ public void forget(Xid xid) throws XAException, RemoteException;
+
+ public int getTransactionTimeout() throws XAException, RemoteException;
+
+ public int prepare(Xid xid) throws XAException, RemoteException;
+
+ public Xid[] recover(int flag) throws XAException, RemoteException;
+
+ public void rollback(Xid xid) throws XAException, RemoteException;
+
+ public boolean setTransactionTimeout(int seconds) throws XAException, RemoteException;
+
+ public void start(Xid xid, int flags) throws XAException, RemoteException;
+
+ // Note: This provides distributed isSameRM
+ public Serializable getRMId() throws RemoteException;
+}
Copied: trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResourceWrapperXAResource.java (from rev 690, branches/mgr-73/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResourceWrapperXAResource.java)
===================================================================
--- trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResourceWrapperXAResource.java (rev 0)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/RemoteXAResourceWrapperXAResource.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2007 The Topaz Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+package org.mulgara.server.rmi;
+
+// Java 2 standard packages
+import java.io.Serializable;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.Arrays;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import org.apache.log4j.*;
+
+// Local packages
+import org.mulgara.server.ResourceManagerInstanceAdaptor;
+
+/**
+ * Wraps an RMI RemoteXAResource and presents it as a pure XAResource.
+ *
+ * @created 2007-11-28
+ * @author Andrae Muys
+ * @licence Apache License v2.0
+ */
+class RemoteXAResourceWrapperXAResource implements XAResource, ResourceManagerInstanceAdaptor {
+ /** logger */
+ private static Logger logger =
+ Logger.getLogger(RemoteXAResourceWrapperXAResource.class.getName());
+
+ /**
+ * The wrapped instance.
+ */
+ private RemoteXAResource remoteResource;
+
+ /**
+ * Wrap a {@link RemoteAnswer} to make it into an {@link Answer}.
+ *
+ * @param remoteAnswer the instance to wrap
+ * @throws IllegalArgumentException if <var>remoteAnswer</var> is
+ * <code>null</code>
+ */
+ RemoteXAResourceWrapperXAResource(RemoteXAResource remoteResource) throws RemoteException {
+ if (remoteResource == null) {
+ throw new IllegalArgumentException("Null 'remoteResource' parameter");
+ }
+
+ this.remoteResource = remoteResource;
+ }
+
+ //
+ // Methods implementing XAResource
+ //
+
+ public void commit(Xid xid, boolean onePhase) throws XAException {
+ try {
+ remoteResource.commit(convertXid(xid), onePhase);
+ } catch (RemoteException re) {
+ logger.warn("RMI Error in XAResource", re);
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+
+ public void end(Xid xid, int flags) throws XAException {
+ try {
+ remoteResource.end(convertXid(xid), flags);
+ } catch (RemoteException re) {
+ logger.warn("RMI Error in XAResource", re);
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+
+ public void forget(Xid xid) throws XAException {
+ try {
+ remoteResource.forget(convertXid(xid));
+ } catch (RemoteException re) {
+ logger.warn("RMI Error in XAResource", re);
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+
+ public int getTransactionTimeout() throws XAException {
+ try {
+ return remoteResource.getTransactionTimeout();
+ } catch (RemoteException re) {
+ logger.warn("RMI Error in XAResource", re);
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+
+ public boolean isSameRM(XAResource xares) throws XAException {
+ try {
+ if (xares == this) {
+ return true;
+ } else if (xares instanceof ResourceManagerInstanceAdaptor) {
+ try {
+ return ((ResourceManagerInstanceAdaptor)xares).getRMId().equals(remoteResource.getRMId());
+ } catch (UnsupportedOperationException eu) {
+ logger.debug("getRMId() unsupported on XAResource", eu);
+ return false;
+ }
+ } else {
+ return false;
+ }
+ } catch (RemoteException re) {
+ logger.warn("RMI Error in XAResource", re);
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+
+ public Serializable getRMId() {
+ try {
+ return remoteResource.getRMId();
+ } catch (RemoteException er) {
+ throw new UnsupportedOperationException("Failed to obtain RMid", er);
+ }
+ }
+
+ public int prepare(Xid xid) throws XAException {
+ try {
+ return remoteResource.prepare(convertXid(xid));
+ } catch (RemoteException re) {
+ logger.warn("RMI Error in XAResource", re);
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+
+ public Xid[] recover(int flag) throws XAException {
+ try {
+ return remoteResource.recover(flag);
+ } catch (RemoteException re) {
+ logger.warn("RMI Error in XAResource", re);
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+
+ public void rollback(Xid xid) throws XAException {
+ try {
+ remoteResource.rollback(convertXid(xid));
+ } catch (RemoteException re) {
+ logger.warn("RMI Error in XAResource", re);
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+
+ public boolean setTransactionTimeout(int seconds) throws XAException {
+ try {
+ return remoteResource.setTransactionTimeout(seconds);
+ } catch (RemoteException re) {
+ logger.warn("RMI Error in XAResource", re);
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+
+ public void start(Xid xid, int flags) throws XAException {
+ try {
+ remoteResource.start(convertXid(xid), flags);
+ } catch (RemoteException re) {
+ logger.warn("RMI Error in XAResource", re);
+ throw new XAException(XAException.XAER_RMFAIL);
+ }
+ }
+
+ private SerializableXid convertXid(Xid xid) {
+ return new SerializableXid(xid);
+ }
+
+ private static class SerializableXid implements Xid, Serializable {
+ private byte[] bq;
+ private int fi;
+ private byte[] gtid;
+
+ public SerializableXid(Xid xid) {
+ byte[] tbq = xid.getBranchQualifier();
+ byte[] tgtid = xid.getGlobalTransactionId();
+ this.bq = new byte[tbq.length];
+ this.fi = xid.getFormatId();
+ this.gtid = new byte[tgtid.length];
+ System.arraycopy(tbq, 0, this.bq, 0, tbq.length);
+ System.arraycopy(tgtid, 0, this.gtid, 0, tgtid.length);
+ }
+
+ public byte[] getBranchQualifier() {
+ return bq;
+ }
+
+ public int getFormatId() {
+ return fi;
+ }
+
+ public byte[] getGlobalTransactionId() {
+ return gtid;
+ }
+ }
+}
Modified: trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/SessionWrapperRemoteSession.java
===================================================================
--- trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/SessionWrapperRemoteSession.java 2008-03-18 07:02:24 UTC (rev 690)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/SessionWrapperRemoteSession.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -16,7 +16,8 @@
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
- * Contributor(s): N/A.
+ * Contributor(s):
+ * XAResource access copyright 2007 The Topaz Foundation.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
@@ -515,6 +516,24 @@
session.login(securityDomain, username, password);
}
+
+ public RemoteXAResource getXAResource() throws QueryException, RemoteException {
+ try {
+ return new XAResourceWrapperRemoteXAResource(session.getXAResource());
+ } catch (Throwable t) {
+ throw convertToQueryException(t);
+ }
+ }
+
+ public RemoteXAResource getReadOnlyXAResource() throws QueryException, RemoteException {
+ try {
+ return new XAResourceWrapperRemoteXAResource(session.getReadOnlyXAResource());
+ } catch (Throwable t) {
+ throw convertToQueryException(t);
+ }
+ }
+
+
// Construct an exception chain that will pass over RMI.
protected Throwable mapThrowable(Throwable t) {
Throwable cause = t.getCause();
Copied: trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/XAResourceWrapperRemoteXAResource.java (from rev 690, branches/mgr-73/src/jar/server-rmi/java/org/mulgara/server/rmi/XAResourceWrapperRemoteXAResource.java)
===================================================================
--- trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/XAResourceWrapperRemoteXAResource.java (rev 0)
+++ trunk/src/jar/server-rmi/java/org/mulgara/server/rmi/XAResourceWrapperRemoteXAResource.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2007 The Topaz Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ * Contributions:
+ */
+package org.mulgara.server.rmi;
+
+// Java 2 standard packages
+import java.io.Serializable;
+import java.rmi.RemoteException;
+import java.rmi.server.UnicastRemoteObject;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+// Third party packages
+import org.apache.log4j.*;
+
+// Local packages
+import org.mulgara.server.ResourceManagerInstanceAdaptor;
+
+/**
+ * Wraps an XAResource and makes it available as an RMI object.
+ *
+ * @created 2007-11-28
+ * @author Andrae Muys
+ * @licence Apache License v2.0
+ */
+public class XAResourceWrapperRemoteXAResource
+ extends UnicastRemoteObject implements RemoteXAResource {
+ /** logger */
+ private static Logger logger =
+ Logger.getLogger(XAResourceWrapperRemoteXAResource.class.getName());
+
+ protected final XAResource resource;
+ protected final ResourceManagerInstanceAdaptor adaptor;
+
+ public XAResourceWrapperRemoteXAResource(XAResource resource) throws RemoteException {
+ if (resource == null) {
+ throw new IllegalArgumentException("Null 'resource' parameter");
+ }
+
+ this.resource = resource;
+ this.adaptor = resource instanceof ResourceManagerInstanceAdaptor ?
+ (ResourceManagerInstanceAdaptor)resource : null;
+ }
+
+ public void commit(Xid xid, boolean onePhase) throws XAException, RemoteException {
+ resource.commit(xid, onePhase);
+ }
+
+ public void end(Xid xid, int flags) throws XAException, RemoteException {
+ resource.end(xid, flags);
+ }
+
+ public void forget(Xid xid) throws XAException, RemoteException {
+ resource.forget(xid);
+ }
+
+ public int getTransactionTimeout() throws XAException, RemoteException {
+ return resource.getTransactionTimeout();
+ }
+
+ public boolean isSameRM(XAResource xares) throws XAException, RemoteException {
+ return resource.isSameRM(xares);
+ }
+
+ public int prepare(Xid xid) throws XAException, RemoteException {
+ return resource.prepare(xid);
+ }
+
+ public Xid[] recover(int flag) throws XAException, RemoteException {
+ return resource.recover(flag);
+ }
+
+ public void rollback(Xid xid) throws XAException, RemoteException {
+ resource.rollback(xid);
+ }
+
+ public boolean setTransactionTimeout(int seconds) throws XAException, RemoteException {
+ return resource.setTransactionTimeout(seconds);
+ }
+
+ public void start(Xid xid, int flags) throws XAException, RemoteException {
+ resource.start(xid, flags);
+ }
+
+ public Serializable getRMId() throws RemoteException, UnsupportedOperationException {
+ if (adaptor == null) {
+ throw new UnsupportedOperationException("Wrapped XAResource does not support remote-id");
+ } else {
+ return adaptor.getRMId();
+ }
+ }
+}
Copied: trunk/src/jar/util/java/org/mulgara/util/Assoc1toNMap.java (from rev 690, branches/mgr-73/src/jar/util/java/org/mulgara/util/Assoc1toNMap.java)
===================================================================
--- trunk/src/jar/util/java/org/mulgara/util/Assoc1toNMap.java (rev 0)
+++ trunk/src/jar/util/java/org/mulgara/util/Assoc1toNMap.java 2008-03-18 09:32:14 UTC (rev 691)
@@ -0,0 +1,102 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.rosenlaw.com/OSL3.0.htm
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * This file is an original work developed by Netymon Pty Ltd
+ * (http://www.netymon.com, mailto:mail at netymon.com) under contract to
+ * Topaz Foundation. Portions created under this contract are
+ * Copyright (c) 2007 Topaz Foundation
+ * All Rights Reserved.
+ */
+
+package org.mulgara.util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A 1:N Associative Map.
+ *
+ * @created 2007-11-12
+ *
+ * @author <a href="mailto:andrae at netymon.com">Andrae Muys</a>
+ *
+ * @company <A href="mailto:mail at netymon.com">Netymon Pty Ltd</A>
+ *
+ * @copyright ©2007 <a href="http://www.topazproject.org/">Topaz Project</a>
+ *
+ * @licence Open Software License v3.0</a>
+ */
+
+public class Assoc1toNMap<T1,T2> implements Iterable<Map.Entry<T1,Set<T2>>> {
+ private Map<T1, Set<T2>> map1toN;
+ private Map<T2, T1> mapNto1;
+
+ public Assoc1toNMap() {
+ map1toN = new HashMap<T1, Set<T2>>();
+ mapNto1 = new HashMap<T2, T1>();
+ }
+
+ public T1 get1(T2 t2) {
+ return mapNto1.get(t2);
+ }
+
+ public Set<T2> getN(T1 t1) {
+ return map1toN.get(t1);
+ }
+
+ public void put(T1 t1, T2 t2) {
+ Set<T2> t2set = getN(t1);
+ if (t2set == null) {
+ t2set = new HashSet<T2>();
+ map1toN.put(t1, t2set);
+ }
+ t2set.add(t2);
+
+ mapNto1.put(t2, t1);
+ }
+
+ public void remove1(T1 t1) {
+ Set<T2> t2set = map1toN.remove(t1);
+ for (T2 t2 : t2set) {
+ mapNto1.remove(t2);
+ }
+ }
+
+ public void removeN(T2 t2) {
+ T1 t1 = mapNto1.remove(t2);
+ if (t1 != null) {
+ Set<T2> t2set = map1toN.get(t1);
+ t2set.remove(t2);
+ if (t2set.isEmpty()) {
+ map1toN.remove(t1);
+ }
+ }
+ }
+
+ public boolean contains1(T1 t1) {
+ return map1toN.containsKey(t1);
+ }
+
+ public boolean containsN(T2 t2) {
+ return mapNto1.containsKey(t2);
+ }
+
+ public Set<T1> getKeySet() {
+ return map1toN.keySet();
+ }
+
+ public Iterator<Map.Entry<T1,Set<T2>>> iterator() {
+ return map1toN.entrySet().iterator();
+ }
+}
More information about the Mulgara-svn
mailing list