GERONIMO-3344 non-kernel-dependent tx and connector classes in separate jars

git-svn-id: https://svn.apache.org/repos/asf/geronimo/components/txmanager/trunk@559235 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/geronimo-connector/LICENSE.txt b/geronimo-connector/LICENSE.txt
new file mode 100644
index 0000000..6b0b127
--- /dev/null
+++ b/geronimo-connector/LICENSE.txt
@@ -0,0 +1,203 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
+
diff --git a/geronimo-connector/NOTICE.txt b/geronimo-connector/NOTICE.txt
new file mode 100644
index 0000000..3b4090d
--- /dev/null
+++ b/geronimo-connector/NOTICE.txt
@@ -0,0 +1,6 @@
+Apache Geronimo 
+Copyright 2003-2006 The Apache Software Foundation
+
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
+
diff --git a/geronimo-connector/pom.xml b/geronimo-connector/pom.xml
new file mode 100644
index 0000000..9b52593
--- /dev/null
+++ b/geronimo-connector/pom.xml
@@ -0,0 +1,426 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You 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.
+-->
+
+<!-- $Rev$ $Date$ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.geronimo.genesis.config</groupId>
+        <artifactId>project-config</artifactId>
+        <version>1.2-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.apache.geronimo.components</groupId>
+    <artifactId>geronimo-connector</artifactId>
+    <version>2.0-SNAPSHOT</version>
+    <name>Geronimo :: Connector</name>
+
+    <dependencies>
+<!--        
+        <dependency>
+            <groupId>org.apache.geronimo.modules</groupId>
+            <artifactId>geronimo-naming</artifactId>
+            <version>${version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.geronimo.modules</groupId>
+            <artifactId>geronimo-deployment</artifactId>
+            <version>${version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.geronimo.modules</groupId>
+            <artifactId>geronimo-security</artifactId>
+            <version>${version}</version>
+        </dependency>
+-->
+        <!-- g-system, g-management come from this via g-j2ee -->
+        <dependency>
+            <groupId>org.apache.geronimo.components</groupId>
+            <artifactId>geronimo-transaction</artifactId>
+            <version>${version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-j2ee-connector_1.5_spec</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+<!--
+        <dependency>
+            <groupId>org.tranql</groupId>
+            <artifactId>tranql</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>commons-logging</groupId>
+                    <artifactId>commons-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+-->
+        <!-- needed for passwordcredential realm-->
+<!--
+        <dependency>
+            <groupId>regexp</groupId>
+            <artifactId>regexp</artifactId>
+        </dependency>
+-->
+    </dependencies>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.geronimo.plugins</groupId>
+                    <artifactId>car-maven-plugin</artifactId>
+                    <version>${version}</version>
+                    <extensions>true</extensions>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-enforcer-plugin</artifactId>
+                    <version>1.0-alpha-2</version>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-dependency-plugin</artifactId>
+                    <version>2.0-alpha-4</version>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>groovy-maven-plugin</artifactId>
+                    <version>1.0-alpha-2</version>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>selenium-maven-plugin</artifactId>
+                    <version>1.0-beta-2-SNAPSHOT</version>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>xmlbeans-maven-plugin</artifactId>
+                    <version>2.3.1-20070720.222301-1</version>
+
+                    <executions>
+                        <execution>
+                            <goals>
+                                <goal>xmlbeans</goal>
+                            </goals>
+                        </execution>
+                    </executions>
+
+                    <configuration>
+                        <download>true</download>
+                        <quiet>false</quiet>
+                    </configuration>
+                </plugin>
+
+
+                <plugin>
+                    <groupId>org.codehaus.mojo.jspc</groupId>
+                    <artifactId>jspc-maven-plugin</artifactId>
+                    <version>2.0-SNAPSHOT</version>
+                    <executions>
+                        <execution>
+                            <goals>
+                                 <goal>compile</goal>
+                            </goals>
+                            <configuration>
+                                <warSourceDirectory>${pom.basedir}/src/main/webapp</warSourceDirectory>
+                            </configuration>
+                        </execution>
+                    </executions>
+                    
+                    <!-- Use the Tomcat 6 JSP compiler, but with our custom Jasper version -->
+                    <dependencies>
+                        <dependency>
+                            <groupId>org.codehaus.mojo.jspc</groupId>
+                            <artifactId>jspc-compiler-tomcat6</artifactId>
+                            <version>2.0-SNAPSHOT</version>
+                            <exclusions>
+                                <exclusion>
+                                    <groupId>org.apache.tomcat</groupId>
+                                    <artifactId>jasper</artifactId>
+                                </exclusion>
+                            </exclusions>
+                        </dependency>
+                        
+                        <dependency>
+                            <groupId>org.apache.tomcat</groupId>
+                            <artifactId>jasper</artifactId>
+                            <version>6.0.13-G543818</version>
+                        </dependency>
+                    </dependencies>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.codehaus.mojo</groupId>
+                    <artifactId>sql-maven-plugin</artifactId>
+                    <version>1.0</version>
+                </plugin>
+                
+                <!--
+                FIXME: Should not configure war to assume jsp by default
+                -->
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-war-plugin</artifactId>
+                    <version>2.0.2</version>
+                    <configuration>
+                        <warSourceDirectory>${pom.basedir}/src/main/webapp</warSourceDirectory>
+                        <webXml>${project.build.directory}/jspweb.xml</webXml>
+                        <archiveClasses>true</archiveClasses>
+                        <archive>
+                            <!-- Do not include META-INF/maven to avoid long file problems on windows -->
+                            <addMavenDescriptor>false</addMavenDescriptor>
+                        </archive>
+                        
+                        <!--
+                        HACK: Include legal files explicity, otherwise they will end up in the wrong path
+                              or in another jar file in the war.
+                        
+                        NOTE: targetPath is broken for webResources (as documented)
+                        -->
+                        <webResources>
+                            <resource>
+                                <directory>${project.build.outputDirectory}</directory>
+                                <includes>
+                                    <include>META-INF/LICENSE*</include>
+                                    <include>META-INF/NOTICE*</include>
+                                    <include>META-INF/DISCLAIMER*</include>
+                                </includes>
+                            </resource>
+                        </webResources>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-ear-plugin</artifactId>
+                    <version>2.3</version>
+                    <configuration>
+                        <archive>
+                            <!-- Do not include META-INF/maven to avoid long file problems on windows -->
+                            <addMavenDescriptor>false</addMavenDescriptor>
+                        </archive>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-rar-plugin</artifactId>
+                    <version>2.2</version>
+                    <configuration>
+                        <archive>
+                            <!-- Do not include META-INF/maven to avoid long file problems on windows -->
+                            <addMavenDescriptor>false</addMavenDescriptor>
+                        </archive>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-jar-plugin</artifactId>
+                    <configuration>
+                        <archive>
+                            <!-- Do not include META-INF/maven to avoid long file problems on windows -->
+                            <addMavenDescriptor>false</addMavenDescriptor>
+                        </archive>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.geronimo.plugins</groupId>
+                    <artifactId>geronimo-maven-plugin</artifactId>
+                    <version>${version}</version>
+
+                    <configuration>
+                        <assemblies>
+                            <assembly>
+                                <id>jetty</id>
+                                <groupId>org.apache.geronimo.assemblies</groupId>
+                                <artifactId>geronimo-jetty6-jee5</artifactId>
+                                <version>${version}</version>
+                                <classifier>bin</classifier>
+                                <type>zip</type>
+                            </assembly>
+
+                            <assembly>
+                                <id>jetty-minimal</id>
+                                <groupId>org.apache.geronimo.assemblies</groupId>
+                                <artifactId>geronimo-jetty6-minimal</artifactId>
+                                <version>${version}</version>
+                                <classifier>bin</classifier>
+                                <type>zip</type>
+                            </assembly>
+
+                            <assembly>
+                                <id>tomcat</id>
+                                <groupId>org.apache.geronimo.assemblies</groupId>
+                                <artifactId>geronimo-tomcat6-jee5</artifactId>
+                                <version>${version}</version>
+                                <classifier>bin</classifier>
+                                <type>zip</type>
+                            </assembly>
+
+                            <assembly>
+                                <id>tomcat-minimal</id>
+                                <groupId>org.apache.geronimo.assemblies</groupId>
+                                <artifactId>geronimo-tomcat6-minimal</artifactId>
+                                <version>${version}</version>
+                                <classifier>bin</classifier>
+                                <type>zip</type>
+                            </assembly>
+                            
+                            <assembly>
+                                <id>framework</id>
+                                <groupId>org.apache.geronimo.assemblies</groupId>
+                                <artifactId>geronimo-framework</artifactId>
+                                <version>${version}</version>
+                                <classifier>bin</classifier>
+                                <type>zip</type>
+                            </assembly>
+                        </assemblies>
+
+                        <defaultAssemblyId>jetty</defaultAssemblyId>
+
+                        <optionSets>
+                            <optionSet>
+                                <id>morememory</id>
+                                <options>
+                                    <option>-Xmx512m</option>
+                                    <option>-XX:MaxPermSize=128m</option>
+                                </options>
+                            </optionSet>
+
+                            <optionSet>
+                                <id>debug</id>
+                                <options>
+                                    <option>-Xdebug</option>
+                                    <option>-Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n</option>
+                                </options>
+                            </optionSet>
+                        </optionSets>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>validate</phase>
+                        <goals>
+                            <goal>enforce</goal>
+                        </goals>
+                        <configuration>
+                            <rules>
+                                <!-- Allow any Java >= 1.5, but not 1.6 or above -->
+                                <requireJavaVersion>
+                                    <version>[1.5,1.6)</version>
+                                </requireJavaVersion>
+                                
+                                <!-- Allow any Maven >= 2.0.5 -->
+                                <requireMavenVersion>
+                                    <version>[2.0.5,)</version>
+                                </requireMavenVersion>
+                            </rules>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            
+            <plugin>
+                <groupId>org.apache.geronimo.genesis.plugins</groupId>
+                <artifactId>tools-maven-plugin</artifactId>
+
+                <!-- Tools includes custom packagings, install as extension to pick them up -->
+                <extensions>true</extensions>
+
+                <executions>
+                    <execution>
+                        <id>install-legal-files</id>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>copy-legal-files</goal>
+                        </goals>
+                        <configuration>
+                            <!-- Fail the build if no legal files were copied -->
+                            <strict>true</strict>
+                        </configuration>
+                    </execution>
+
+                    <execution>
+                        <id>verify-legal-files</id>
+                        <phase>verify</phase>
+                        <goals>
+                            <goal>verify-legal-files</goal>
+                        </goals>
+                        <configuration>
+                            <!-- Fail the build if no legal files were found -->
+                            <strict>true</strict>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.5</source>
+                    <target>1.5</target>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-idea-plugin</artifactId>
+                <configuration>
+                    <jdkName>1.5</jdkName>
+                    <jdkLevel>1.5</jdkLevel>
+                    <linkModules>true</linkModules>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-release-plugin</artifactId>
+                <configuration>
+                    <tagBase>https://svn.apache.org/repos/asf/geronimo/server/tags</tagBase>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
+
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/ActivationSpecWrapper.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/ActivationSpecWrapper.java
new file mode 100644
index 0000000..fd2ddb1
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/ActivationSpecWrapper.java
@@ -0,0 +1,111 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.ActivationSpec;
+import javax.resource.spi.ResourceAdapter;
+import javax.resource.spi.endpoint.MessageEndpointFactory;
+
+/**
+ * Wrapper for ActivationSpec instances.
+ * The framework assumes all RequiredConfigProperties are of type String, although it
+ * is unclear if this is required by the spec.
+ *
+ * @version $Rev$ $Date$
+ */
+public class ActivationSpecWrapper {
+
+    protected final ActivationSpec activationSpec;
+
+    private final ResourceAdapterWrapper resourceAdapterWrapper;
+    private final String containerId;
+
+    /**
+     * Default constructor required when a class is used as a GBean Endpoint.
+     */
+    public ActivationSpecWrapper() {
+        activationSpec = null;
+        containerId = null;
+        resourceAdapterWrapper = null;
+    }
+
+    /**
+     * Normal managed constructor.
+     *
+     * @param activationSpecClass Class of admin object to be wrapped.
+     * @throws IllegalAccessException
+     * @throws InstantiationException
+     */
+    public ActivationSpecWrapper(final String activationSpecClass,
+                                 final String containerId,
+                                 final ResourceAdapterWrapper resourceAdapterWrapper,
+                                 final ClassLoader cl) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
+        Class clazz = cl.loadClass(activationSpecClass);
+        this.activationSpec = (ActivationSpec) clazz.newInstance();
+        this.containerId = containerId;
+        this.resourceAdapterWrapper = resourceAdapterWrapper;
+    }
+
+    /**
+     */
+    public ActivationSpecWrapper(ActivationSpec activationSpec, ResourceAdapterWrapper resourceAdapterWrapper)  {
+        this.activationSpec = activationSpec;
+        this.resourceAdapterWrapper = resourceAdapterWrapper;
+        this.containerId = null;
+    }
+
+    /**
+     * Returns class of wrapped ActivationSpec.
+     *
+     * @return class of wrapped ActivationSpec
+     */
+//    public String getActivationSpecClass() {
+//        return activationSpecClass;
+//    }
+
+    public String getContainerId() {
+        return containerId;
+    }
+
+    public ResourceAdapterWrapper getResourceAdapterWrapper() {
+        return resourceAdapterWrapper;
+    }
+
+
+    //GBeanLifecycle implementation
+    public void activate(final MessageEndpointFactory messageEndpointFactory) throws ResourceException {
+        ResourceAdapter resourceAdapter = activationSpec.getResourceAdapter();
+        if (resourceAdapter == null) {
+            resourceAdapterWrapper.registerResourceAdapterAssociation(activationSpec);
+        }
+        resourceAdapterWrapper.endpointActivation(messageEndpointFactory, activationSpec);
+        resourceAdapterWrapper.doRecovery(activationSpec, containerId);
+    }
+
+    public void deactivate(final MessageEndpointFactory messageEndpointFactory) {
+        ResourceAdapter resourceAdapter = activationSpec.getResourceAdapter();
+        if (resourceAdapter != null) {
+            resourceAdapterWrapper.endpointDeactivation(messageEndpointFactory, activationSpec);
+        } else {
+            //this should never happen, activation spec should have been registered with r.a.
+            throw new IllegalStateException("ActivationSpec was never registered with ResourceAdapter");
+        }
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/ConnectionReleaser.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/ConnectionReleaser.java
new file mode 100644
index 0000000..961eb85
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/ConnectionReleaser.java
@@ -0,0 +1,27 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public interface ConnectionReleaser {
+    void afterCompletion(Object managedConnectionInfo);
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/ConnectorTransactionContext.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/ConnectorTransactionContext.java
new file mode 100644
index 0000000..ca519a0
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/ConnectorTransactionContext.java
@@ -0,0 +1,126 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.transaction.Status;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+
+import org.apache.geronimo.connector.outbound.TransactionCachingInterceptor;
+import org.apache.geronimo.transaction.manager.TransactionImpl;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class ConnectorTransactionContext {
+    private static final ConcurrentHashMap<Transaction, ConnectorTransactionContext> DATA_INDEX = new ConcurrentHashMap<Transaction, ConnectorTransactionContext>();
+
+    public static ConnectorTransactionContext get(Transaction transaction) {
+        if (transaction == null) {
+            throw new NullPointerException("transaction is null");
+        }
+
+        ConnectorTransactionContext ctx = DATA_INDEX.get(transaction);
+        if (ctx == null) {
+            ctx = new ConnectorTransactionContext();
+
+            try {
+                int status = transaction.getStatus();
+                if (status != Status.STATUS_COMMITTED && status != Status.STATUS_ROLLEDBACK && status != Status.STATUS_UNKNOWN) {
+                    ((TransactionImpl)transaction).registerInterposedSynchronization(new ConnectorSynchronization(ctx, transaction));
+                    // Note: no synchronization is necessary here.  Since a transaction can only be associated with a single
+                    // thread at a time, it should not be possible for someone else to have snuck in and created a
+                    // ConnectorTransactionContext for this transaction.  We still protect against that with the putIfAbsent
+                    // call below, and we simply have an extra transaction synchronization registered that won't do anything
+                    DATA_INDEX.putIfAbsent(transaction, ctx);
+                }
+//            } catch (RollbackException e) {
+//                throw (IllegalStateException) new IllegalStateException("Transaction is already rolled back").initCause(e);
+            } catch (SystemException e) {
+                throw new RuntimeException("Unable to register ejb transaction synchronization callback", e);
+            }
+
+        }
+        return ctx;
+    }
+
+    public static TransactionCachingInterceptor.ManagedConnectionInfos get(Transaction transaction, ConnectionReleaser key) {
+        ConnectorTransactionContext ctx = get(transaction);
+        TransactionCachingInterceptor.ManagedConnectionInfos infos = ctx.getManagedConnectionInfo(key);
+        if (infos == null) {
+            infos = new TransactionCachingInterceptor.ManagedConnectionInfos();
+            ctx.setManagedConnectionInfo(key, infos);
+        }
+        return infos;
+    }
+
+    private static void remove(Transaction transaction) {
+        DATA_INDEX.remove(transaction);
+    }
+
+    private Map<ConnectionReleaser, TransactionCachingInterceptor.ManagedConnectionInfos> managedConnections;
+
+    private synchronized TransactionCachingInterceptor.ManagedConnectionInfos getManagedConnectionInfo(ConnectionReleaser key) {
+        if (managedConnections == null) {
+            return null;
+        }
+        return managedConnections.get(key);
+    }
+
+    private synchronized void setManagedConnectionInfo(ConnectionReleaser key, TransactionCachingInterceptor.ManagedConnectionInfos info) {
+        if (managedConnections == null) {
+            managedConnections = new HashMap<ConnectionReleaser, TransactionCachingInterceptor.ManagedConnectionInfos>();
+        }
+        managedConnections.put(key, info);
+    }
+
+    private static class ConnectorSynchronization implements Synchronization {
+        private final ConnectorTransactionContext ctx;
+        private final Transaction transaction;
+
+        public ConnectorSynchronization(ConnectorTransactionContext ctx, Transaction transaction) {
+            this.ctx = ctx;
+            this.transaction = transaction;
+        }
+
+        public void beforeCompletion() {
+        }
+
+        public void afterCompletion(int status) {
+            try {
+                synchronized (ctx) {
+                    if (ctx.managedConnections != null) {
+                        for (Map.Entry<ConnectionReleaser, TransactionCachingInterceptor.ManagedConnectionInfos> entry : ctx.managedConnections.entrySet()) {
+                            ConnectionReleaser key = entry.getKey();
+                            key.afterCompletion(entry.getValue());
+                        }
+                        //If BeanTransactionContext never reuses the same instance for sequential BMT, this
+                        //clearing is unnecessary.
+                        ctx.managedConnections.clear();
+                    }
+                }
+            } finally {
+                remove(transaction);
+            }
+        }
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/GeronimoBootstrapContext.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/GeronimoBootstrapContext.java
new file mode 100644
index 0000000..09d7f5a
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/GeronimoBootstrapContext.java
@@ -0,0 +1,75 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector;
+
+import java.util.Timer;
+
+import javax.resource.spi.UnavailableException;
+import javax.resource.spi.XATerminator;
+import javax.resource.spi.work.WorkManager;
+
+/**
+ * GBean BootstrapContext implementation that refers to externally configured WorkManager
+ * and XATerminator gbeans.
+ *
+ * @version $Rev$ $Date$
+ */
+public class GeronimoBootstrapContext implements javax.resource.spi.BootstrapContext {
+    private final WorkManager workManager;
+    private final XATerminator xATerminator;
+
+    /**
+     * Default constructor for use as a GBean Endpoint.
+     */
+    public GeronimoBootstrapContext() {
+        workManager = null;
+        xATerminator = null;
+    }
+
+    /**
+     * Normal constructor for use as a GBean.
+     * @param workManager
+     * @param xaTerminator
+     */
+    public GeronimoBootstrapContext(WorkManager workManager, XATerminator xaTerminator) {
+        this.workManager = workManager;
+        this.xATerminator = xaTerminator;
+    }
+
+
+    /**
+     * @see javax.resource.spi.BootstrapContext#getWorkManager()
+     */
+    public WorkManager getWorkManager() {
+        return workManager;
+    }
+
+    /**
+     * @see javax.resource.spi.BootstrapContext#getXATerminator()
+     */
+    public XATerminator getXATerminator() {
+        return xATerminator;
+    }
+
+    /**
+     * @see javax.resource.spi.BootstrapContext#createTimer()
+     */
+    public Timer createTimer() throws UnavailableException {
+        return new Timer();
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/ResourceAdapterWrapper.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/ResourceAdapterWrapper.java
new file mode 100644
index 0000000..42df1e0
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/ResourceAdapterWrapper.java
@@ -0,0 +1,160 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector;
+
+import java.util.Map;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.ActivationSpec;
+import javax.resource.spi.BootstrapContext;
+import javax.resource.spi.ResourceAdapter;
+import javax.resource.spi.ResourceAdapterAssociation;
+import javax.resource.spi.ResourceAdapterInternalException;
+import javax.resource.spi.endpoint.MessageEndpointFactory;
+import javax.transaction.SystemException;
+import javax.transaction.xa.XAResource;
+
+import org.apache.geronimo.transaction.manager.NamedXAResource;
+import org.apache.geronimo.transaction.manager.RecoverableTransactionManager;
+import org.apache.geronimo.transaction.manager.WrapperNamedXAResource;
+
+/**
+ * Dynamic GBean wrapper around a ResourceAdapter object, exposing the config-properties as
+ * GBean attributes.
+ *
+ * @version $Rev$ $Date$
+ */
+public class ResourceAdapterWrapper implements ResourceAdapter {
+
+    private final String name;
+
+    private final String resourceAdapterClass;
+
+    private final BootstrapContext bootstrapContext;
+
+    protected final ResourceAdapter resourceAdapter;
+
+    private final Map<String,String> messageListenerToActivationSpecMap;
+
+    private final RecoverableTransactionManager transactionManager;
+
+
+    /**
+     *  default constructor for enhancement proxy endpoint
+     */
+    public ResourceAdapterWrapper() {
+        this.name = null;
+        this.resourceAdapterClass = null;
+        this.bootstrapContext = null;
+        this.resourceAdapter = null;
+        this.messageListenerToActivationSpecMap = null;
+        this.transactionManager = null;
+    }
+
+    public ResourceAdapterWrapper(String name,
+            String resourceAdapterClass,
+            Map<String, String> messageListenerToActivationSpecMap,
+            BootstrapContext bootstrapContext,
+            RecoverableTransactionManager transactionManager,
+            ClassLoader cl) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+        this.name = name;
+        this.resourceAdapterClass = resourceAdapterClass;
+        this.bootstrapContext = bootstrapContext;
+        Class clazz = cl.loadClass(resourceAdapterClass);
+        resourceAdapter = (ResourceAdapter) clazz.newInstance();
+        this.messageListenerToActivationSpecMap = messageListenerToActivationSpecMap;
+        this.transactionManager = transactionManager;
+    }
+    
+    public ResourceAdapterWrapper(String name, ResourceAdapter resourceAdapter, Map<String, String> messageListenerToActivationSpecMap, BootstrapContext bootstrapContext, RecoverableTransactionManager transactionManager) {
+        this.name = name;
+        this.resourceAdapterClass = resourceAdapter.getClass().getName();
+        this.bootstrapContext = bootstrapContext;
+        this.resourceAdapter = resourceAdapter;
+        this.messageListenerToActivationSpecMap = messageListenerToActivationSpecMap;
+        this.transactionManager = transactionManager;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getResourceAdapterClass() {
+        return resourceAdapterClass;
+    }
+
+    public Map<String,String> getMessageListenerToActivationSpecMap() {
+        return messageListenerToActivationSpecMap;
+    }
+
+    public ResourceAdapter getResourceAdapter() {
+        return resourceAdapter;
+    }
+
+    public void registerResourceAdapterAssociation(final ResourceAdapterAssociation resourceAdapterAssociation) throws ResourceException {
+        resourceAdapterAssociation.setResourceAdapter(resourceAdapter);
+    }
+
+    public void start(BootstrapContext ctx) throws ResourceAdapterInternalException {
+        throw new IllegalStateException("Don't call this");
+    }
+
+    public void stop() {
+        throw new IllegalStateException("Don't call this");
+    }
+
+    //endpoint handling
+    public void endpointActivation(final MessageEndpointFactory messageEndpointFactory, final ActivationSpec activationSpec) throws ResourceException {
+        resourceAdapter.endpointActivation(messageEndpointFactory, activationSpec);
+    }
+
+    public void doRecovery(ActivationSpec activationSpec, String containerId) {
+        try {
+            XAResource[] xaResources = getXAResources(new ActivationSpec[]{activationSpec});
+            if (xaResources == null || xaResources.length == 0) {
+                return;
+            }
+            NamedXAResource xaResource = new WrapperNamedXAResource(xaResources[0], containerId);
+            transactionManager.recoverResourceManager(xaResource);
+        } catch (ResourceException e) {
+            transactionManager.recoveryError((SystemException) new SystemException("Could not get XAResource for recovery for mdb: " + containerId).initCause(e));
+        }
+
+    }
+
+    public void endpointDeactivation(final MessageEndpointFactory messageEndpointFactory, final ActivationSpec activationSpec) {
+        resourceAdapter.endpointDeactivation(messageEndpointFactory, activationSpec);
+    }
+
+    public XAResource[] getXAResources(ActivationSpec[] specs) throws ResourceException {
+        return resourceAdapter.getXAResources(specs);
+    }
+
+    public void doStart() throws Exception {
+        resourceAdapter.start(bootstrapContext);
+    }
+
+    public void doStop() {
+        resourceAdapter.stop();
+    }
+
+    public void doFail() {
+        resourceAdapter.stop();
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/AbstractConnectionManager.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/AbstractConnectionManager.java
new file mode 100644
index 0000000..a1b7306
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/AbstractConnectionManager.java
@@ -0,0 +1,196 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.ConnectionManager;
+import javax.resource.spi.ConnectionRequestInfo;
+import javax.resource.spi.LazyAssociatableConnectionManager;
+import javax.resource.spi.ManagedConnectionFactory;
+import javax.transaction.SystemException;
+
+import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PoolingSupport;
+import org.apache.geronimo.transaction.manager.NamedXAResource;
+import org.apache.geronimo.transaction.manager.RecoverableTransactionManager;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public abstract class AbstractConnectionManager implements ConnectionManagerContainer, ConnectionManager, LazyAssociatableConnectionManager, PoolingAttributes {
+    protected final Interceptors interceptors;
+    private final RecoverableTransactionManager transactionManager;
+
+    //default constructor for use as endpoint
+    public AbstractConnectionManager() {
+        interceptors = null;
+        transactionManager = null;
+    }
+
+    public AbstractConnectionManager(Interceptors interceptors, RecoverableTransactionManager transactionManager) {
+        this.interceptors = interceptors;
+        this.transactionManager = transactionManager;
+    }
+
+    public Object createConnectionFactory(ManagedConnectionFactory mcf) throws ResourceException {
+        return mcf.createConnectionFactory(this);
+    }
+
+    protected ConnectionManager getConnectionManager() {
+        return this;
+    }
+    
+    public void doRecovery(ManagedConnectionFactory managedConnectionFactory) {
+        try {
+            if (!getIsRecoverable()) {
+                return;
+            }
+            ManagedConnectionInfo mci = new ManagedConnectionInfo(managedConnectionFactory, null);
+
+            ConnectionInfo recoveryConnectionInfo = new ConnectionInfo(mci);
+            getRecoveryStack().getConnection(recoveryConnectionInfo);
+
+            // For pooled resources, we may now have a new MCI (not the one constructed above). Make sure we use the correct MCI
+            NamedXAResource xaResource = (NamedXAResource) recoveryConnectionInfo.getManagedConnectionInfo().getXAResource();
+            if (xaResource != null) {
+                transactionManager.recoverResourceManager(xaResource);
+                getRecoveryStack().returnConnection(recoveryConnectionInfo, ConnectionReturnAction.DESTROY);
+            }
+        } catch (ResourceException e) {
+            transactionManager.recoveryError((SystemException)new SystemException("Could not obtain recovery XAResource for managedConnectionFactory " + managedConnectionFactory).initCause(e));
+        }
+    }
+
+    /**
+     * in: mcf != null, is a deployed mcf
+     * out: useable connection object.
+     */
+    public Object allocateConnection(ManagedConnectionFactory managedConnectionFactory,
+                                     ConnectionRequestInfo connectionRequestInfo)
+            throws ResourceException {
+        ManagedConnectionInfo mci = new ManagedConnectionInfo(managedConnectionFactory, connectionRequestInfo);
+        ConnectionInfo ci = new ConnectionInfo(mci);
+        getStack().getConnection(ci);
+        Object connection = ci.getConnectionProxy();
+        if (connection == null) {
+            connection = ci.getConnectionHandle();
+        }
+        return connection;
+    }
+
+    /**
+     * in: non-null connection object, from non-null mcf.
+     * connection object is not associated with a managed connection
+     * out: supplied connection object is assiciated with a non-null ManagedConnection from mcf.
+     */
+    public void associateConnection(Object connection,
+                                    ManagedConnectionFactory managedConnectionFactory,
+                                    ConnectionRequestInfo connectionRequestInfo)
+            throws ResourceException {
+        ManagedConnectionInfo mci = new ManagedConnectionInfo(managedConnectionFactory, connectionRequestInfo);
+        ConnectionInfo ci = new ConnectionInfo(mci);
+        ci.setConnectionHandle(connection);
+        getStack().getConnection(ci);
+    }
+
+    ConnectionInterceptor getConnectionInterceptor() {
+        return getStack();
+    }
+
+    //statistics
+
+    public int getPartitionCount() {
+        return getPooling().getPartitionCount();
+    }
+
+    public int getPartitionMaxSize() {
+        return getPooling().getPartitionMaxSize();
+    }
+
+    public void setPartitionMaxSize(int maxSize) throws InterruptedException {
+        getPooling().setPartitionMaxSize(maxSize);
+    }
+
+    public int getPartitionMinSize() {
+        return getPooling().getPartitionMinSize();
+    }
+
+    public void setPartitionMinSize(int minSize) {
+        getPooling().setPartitionMinSize(minSize);
+    }
+
+    public int getIdleConnectionCount() {
+        return getPooling().getIdleConnectionCount();
+    }
+
+    public int getConnectionCount() {
+        return getPooling().getConnectionCount();
+    }
+
+    public int getBlockingTimeoutMilliseconds() {
+        return getPooling().getBlockingTimeoutMilliseconds();
+    }
+
+    public void setBlockingTimeoutMilliseconds(int timeoutMilliseconds) {
+        getPooling().setBlockingTimeoutMilliseconds(timeoutMilliseconds);
+    }
+
+    public int getIdleTimeoutMinutes() {
+        return getPooling().getIdleTimeoutMinutes();
+    }
+
+    public void setIdleTimeoutMinutes(int idleTimeoutMinutes) {
+        getPooling().setIdleTimeoutMinutes(idleTimeoutMinutes);
+    }
+
+    private ConnectionInterceptor getStack() {
+        return interceptors.getStack();
+    }
+
+    private ConnectionInterceptor getRecoveryStack() {
+        return interceptors.getRecoveryStack();
+    }
+
+    private boolean getIsRecoverable() {
+        return interceptors.getRecoveryStack() != null;
+    }
+
+    //public for persistence of pooling attributes (max, min size, blocking/idle timeouts)
+    public PoolingSupport getPooling() {
+        return interceptors.getPoolingAttributes();
+    }
+
+    public interface Interceptors {
+        ConnectionInterceptor getStack();
+
+        ConnectionInterceptor getRecoveryStack();
+        
+        PoolingSupport getPoolingAttributes();
+    }
+
+    public void doStart() throws Exception {
+
+    }
+
+    public void doStop() throws Exception {
+        interceptors.getStack().destroy();
+    }
+
+    public void doFail() {
+        interceptors.getStack().destroy();
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/AbstractSinglePoolConnectionInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/AbstractSinglePoolConnectionInterceptor.java
new file mode 100644
index 0000000..e230a35
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/AbstractSinglePoolConnectionInterceptor.java
@@ -0,0 +1,355 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector.outbound;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.ConnectionRequestInfo;
+import javax.resource.spi.ManagedConnectionFactory;
+import javax.security.auth.Subject;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public abstract class AbstractSinglePoolConnectionInterceptor implements ConnectionInterceptor, PoolingAttributes {
+    protected static Log log = LogFactory.getLog(AbstractSinglePoolConnectionInterceptor.class.getName());
+    protected final ConnectionInterceptor next;
+    private final ReadWriteLock resizeLock = new ReentrantReadWriteLock();
+    protected Semaphore permits;
+    protected int blockingTimeoutMilliseconds;
+    protected int connectionCount = 0;
+    private long idleTimeoutMilliseconds;
+    private IdleReleaser idleReleaser;
+    protected Timer timer = PoolIdleReleaserTimer.getTimer();
+    protected int maxSize = 0;
+    protected int minSize = 0;
+    protected int shrinkLater = 0;
+    protected volatile boolean destroyed = false;
+
+    public AbstractSinglePoolConnectionInterceptor(final ConnectionInterceptor next,
+                                                   int maxSize,
+                                                   int minSize,
+                                                   int blockingTimeoutMilliseconds,
+                                                   int idleTimeoutMinutes) {
+        this.next = next;
+        this.maxSize = maxSize;
+        this.minSize = minSize;
+        this.blockingTimeoutMilliseconds = blockingTimeoutMilliseconds;
+        setIdleTimeoutMinutes(idleTimeoutMinutes);
+        permits = new Semaphore(maxSize, true);
+    }
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        if (connectionInfo.getManagedConnectionInfo().getManagedConnection() != null) {
+            if (log.isTraceEnabled()) {
+                log.trace("using already assigned connection " + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to pool " + this);
+            }
+            return;
+        }
+        try {
+            resizeLock.readLock().lock();
+            try {
+                if (permits.tryAcquire(blockingTimeoutMilliseconds, TimeUnit.MILLISECONDS)) {
+                    internalGetConnection(connectionInfo);
+                } else {
+                    throw new ResourceException("No ManagedConnections available "
+                            + "within configured blocking timeout ( "
+                            + blockingTimeoutMilliseconds
+                            + " [ms] ) for pool " + this);
+
+                }
+            } finally {
+                resizeLock.readLock().unlock();
+            }
+
+        } catch (InterruptedException ie) {
+            throw new ResourceException("Interrupted while requesting permit.", ie);
+        } // end of try-catch
+    }
+
+    protected abstract void internalGetConnection(ConnectionInfo connectionInfo) throws ResourceException;
+
+    public void returnConnection(ConnectionInfo connectionInfo,
+                                 ConnectionReturnAction connectionReturnAction) {
+        if (log.isTraceEnabled()) {
+            log.trace("returning connection " + connectionInfo.getConnectionHandle() + " for MCI " + connectionInfo.getManagedConnectionInfo() + " and MC " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to pool " + this);
+        }
+
+        // not strictly synchronized with destroy(), but pooled operations in internalReturn() are...
+        if (destroyed) {
+            try {
+                connectionInfo.getManagedConnectionInfo().getManagedConnection().destroy();
+            } catch (ResourceException re) {
+                // empty
+            }
+            return;
+        }
+
+        resizeLock.readLock().lock();
+        try {
+            ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+            if (connectionReturnAction == ConnectionReturnAction.RETURN_HANDLE && mci.hasConnectionHandles()) {
+                if (log.isTraceEnabled()) {
+                    log.trace("Return request at pool with connection handles! " + connectionInfo.getConnectionHandle() + " for MCI " + connectionInfo.getManagedConnectionInfo() + " and MC " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to pool " + this, new Exception("Stack trace"));
+                }
+                return;
+            }
+
+            boolean wasInPool = internalReturn(connectionInfo, connectionReturnAction);
+
+            if (!wasInPool) {
+                permits.release();
+            }
+        } finally {
+            resizeLock.readLock().unlock();
+        }
+    }
+
+    protected abstract boolean internalReturn(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction);
+
+    protected abstract void internalDestroy();
+
+    // Cancel the IdleReleaser TimerTask (fixes memory leak) and clean up the pool
+    public void destroy() {
+        destroyed = true;
+        if (idleReleaser != null)
+            idleReleaser.cancel();
+        internalDestroy();
+        next.destroy();
+    }
+
+    public int getPartitionCount() {
+        return 1;
+    }
+
+    public abstract int getPartitionMaxSize();
+
+    public void setPartitionMaxSize(int newMaxSize) throws InterruptedException {
+        if (newMaxSize <= 0) {
+            throw new IllegalArgumentException("Max size must be positive, not " + newMaxSize);
+        }
+        if (newMaxSize != getPartitionMaxSize()) {
+            resizeLock.writeLock().lock();
+            try {
+                ResizeInfo resizeInfo = new ResizeInfo(this.minSize, permits.availablePermits(), connectionCount, newMaxSize);
+                this.shrinkLater = resizeInfo.getShrinkLater();
+
+                permits = new Semaphore(newMaxSize, true);
+                //pre-acquire permits for the existing checked out connections that will not be closed when they are returned.
+                for (int i = 0; i < resizeInfo.getTransferCheckedOut(); i++) {
+                    permits.acquire();
+                }
+                //transfer connections we are going to keep
+                transferConnections(newMaxSize, resizeInfo.getShrinkNow());
+                this.minSize = resizeInfo.getNewMinSize();
+            } finally {
+                resizeLock.writeLock().unlock();
+            }
+        }
+    }
+
+
+    static final class ResizeInfo {
+
+        private final int newMinSize;
+        private final int shrinkNow;
+        private final int shrinkLater;
+        private final int transferCheckedOut;
+
+        ResizeInfo(final int oldMinSize, final int oldPermitsAvailable, final int oldConnectionCount, final int newMaxSize) {
+            final int checkedOut = oldConnectionCount - oldPermitsAvailable;
+            int shrinkLater = checkedOut - newMaxSize;
+            if (shrinkLater < 0) {
+                shrinkLater = 0;
+            }
+            this.shrinkLater = shrinkLater;
+            int shrinkNow = oldConnectionCount - newMaxSize - shrinkLater;
+            if (shrinkNow < 0) {
+                shrinkNow = 0;
+            }
+            this.shrinkNow = shrinkNow;
+            if (newMaxSize >= oldMinSize) {
+                newMinSize = oldMinSize;
+            } else {
+                newMinSize = newMaxSize;
+            }
+            this.transferCheckedOut = checkedOut - shrinkLater;
+        }
+
+        public int getNewMinSize() {
+            return newMinSize;
+        }
+
+        public int getShrinkNow() {
+            return shrinkNow;
+        }
+
+        public int getShrinkLater() {
+            return shrinkLater;
+        }
+
+        public int getTransferCheckedOut() {
+            return transferCheckedOut;
+        }
+
+
+    }
+
+    protected abstract void transferConnections(int maxSize, int shrinkNow);
+
+    public abstract int getIdleConnectionCount();
+
+    public int getConnectionCount() {
+        return connectionCount;
+    }
+
+    public int getPartitionMinSize() {
+        return minSize;
+    }
+
+    public void setPartitionMinSize(int minSize) {
+        this.minSize = minSize;
+    }
+
+    public int getBlockingTimeoutMilliseconds() {
+        return blockingTimeoutMilliseconds;
+    }
+
+    public void setBlockingTimeoutMilliseconds(int blockingTimeoutMilliseconds) {
+        if (blockingTimeoutMilliseconds < 0) {
+            throw new IllegalArgumentException("blockingTimeoutMilliseconds must be positive or 0, not " + blockingTimeoutMilliseconds);
+        }
+        if (blockingTimeoutMilliseconds == 0) {
+            this.blockingTimeoutMilliseconds = Integer.MAX_VALUE;
+        } else {
+            this.blockingTimeoutMilliseconds = blockingTimeoutMilliseconds;
+        }
+    }
+
+    public int getIdleTimeoutMinutes() {
+        return (int) idleTimeoutMilliseconds / (1000 * 60);
+    }
+
+    public void setIdleTimeoutMinutes(int idleTimeoutMinutes) {
+        if (idleTimeoutMinutes < 0) {
+            throw new IllegalArgumentException("idleTimeoutMinutes must be positive or 0, not " + idleTimeoutMinutes);
+        }
+        if (idleReleaser != null) {
+            idleReleaser.cancel();
+        }
+        if (idleTimeoutMinutes > 0) {
+            this.idleTimeoutMilliseconds = idleTimeoutMinutes * 60 * 1000;
+            idleReleaser = new IdleReleaser(this);
+            timer.schedule(idleReleaser, this.idleTimeoutMilliseconds, this.idleTimeoutMilliseconds);
+        }
+    }
+
+    protected abstract void getExpiredManagedConnectionInfos(long threshold, ArrayList killList);
+
+    protected abstract boolean addToPool(ManagedConnectionInfo mci);
+
+    // static class to permit chain of strong references from preventing ClassLoaders
+    // from being GC'ed.
+    private static class IdleReleaser extends TimerTask {
+        private AbstractSinglePoolConnectionInterceptor parent;
+
+        private IdleReleaser(AbstractSinglePoolConnectionInterceptor parent) {
+            this.parent = parent;
+        }
+
+        public boolean cancel() {
+            this.parent = null;
+            return super.cancel();
+        }
+
+        public void run() {
+            // protect against interceptor being set to null mid-execution
+            AbstractSinglePoolConnectionInterceptor interceptor = parent;
+            if (interceptor == null)
+                return;
+
+            interceptor.resizeLock.readLock().lock();
+            try {
+                long threshold = System.currentTimeMillis() - interceptor.idleTimeoutMilliseconds;
+                ArrayList killList = new ArrayList(interceptor.getPartitionMaxSize());
+                interceptor.getExpiredManagedConnectionInfos(threshold, killList);
+                for (Iterator i = killList.iterator(); i.hasNext();) {
+                    ManagedConnectionInfo managedConnectionInfo = (ManagedConnectionInfo) i.next();
+                    ConnectionInfo killInfo = new ConnectionInfo(managedConnectionInfo);
+                    interceptor.internalReturn(killInfo, ConnectionReturnAction.DESTROY);
+                }
+                interceptor.permits.release(killList.size());
+            } catch (Throwable t) {
+                log.error("Error occurred during execution of ExpirationMonitor TimerTask", t);
+            } finally {
+                interceptor.resizeLock.readLock().unlock();
+            }
+        }
+
+    }
+
+    // Currently only a short-lived (10 millisecond) task.
+    // So, FillTask, unlike IdleReleaser, shouldn't cause GC problems.
+    protected class FillTask extends TimerTask {
+        private final ManagedConnectionFactory managedConnectionFactory;
+        private final Subject subject;
+        private final ConnectionRequestInfo cri;
+
+        public FillTask(ConnectionInfo connectionInfo) {
+            managedConnectionFactory = connectionInfo.getManagedConnectionInfo().getManagedConnectionFactory();
+            subject = connectionInfo.getManagedConnectionInfo().getSubject();
+            cri = connectionInfo.getManagedConnectionInfo().getConnectionRequestInfo();
+        }
+
+        public void run() {
+            resizeLock.readLock().lock();
+            try {
+                while (connectionCount < minSize) {
+                    ManagedConnectionInfo mci = new ManagedConnectionInfo(managedConnectionFactory, cri);
+                    mci.setSubject(subject);
+                    ConnectionInfo ci = new ConnectionInfo(mci);
+                    try {
+                        next.getConnection(ci);
+                    } catch (ResourceException e) {
+                        return;
+                    }
+                    boolean added = addToPool(mci);
+                    if (!added) {
+                        internalReturn(ci, ConnectionReturnAction.DESTROY);
+                        return;
+                    }
+                }
+            } catch (Throwable t) {
+                log.error("FillTask encountered error in run method", t);
+            } finally {
+                resizeLock.readLock().unlock();
+            }
+        }
+
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionFactorySource.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionFactorySource.java
new file mode 100644
index 0000000..06593cf
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionFactorySource.java
@@ -0,0 +1,37 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public interface ConnectionFactorySource {
+
+    //
+    // This is implemented by "dynamic gbeans" that are swizzled to expose the
+    // getters and setters on the javabean that they wrap.
+    //
+    // The $ is here  so this method couldn't have a name conflict with a javabean property and so it would
+    // not be likely to be called by the casual observer.
+    //
+
+    Object $getResource() throws ResourceException;
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionHandleInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionHandleInterceptor.java
new file mode 100644
index 0000000..fbbf063
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionHandleInterceptor.java
@@ -0,0 +1,77 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+
+/**
+ * ConnectionHandleInterceptor.java
+ *
+ *
+ * @version $Rev$ $Date$
+ */
+public class ConnectionHandleInterceptor implements ConnectionInterceptor {
+
+    private final ConnectionInterceptor next;
+
+    public ConnectionHandleInterceptor(ConnectionInterceptor next) {
+        this.next = next;
+    }
+
+    /**
+     * in: connectionInfo not null, managedConnectionInfo not null. ManagedConnection may or may not be null.  ConnectionHandle may or may not be null
+     * out: managedConnection not null. connection handle not null. managedConnectionInfo has connection handle registered.  Connection handle is associated with ManagedConnection.
+     * @param connectionInfo
+     * @throws ResourceException
+     */
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        next.getConnection(connectionInfo);
+        ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+        if (connectionInfo.getConnectionHandle() == null) {
+            connectionInfo.setConnectionHandle(
+                    mci.getManagedConnection().getConnection(
+                            mci.getSubject(),
+                            mci.getConnectionRequestInfo()));
+            mci.addConnectionHandle(connectionInfo);
+
+        } else if (!mci.hasConnectionInfo(connectionInfo)) {
+            mci.getManagedConnection().associateConnection(
+                    connectionInfo.getConnectionHandle());
+            mci.addConnectionHandle(connectionInfo);
+        }
+        connectionInfo.setTrace();
+    }
+
+    /**
+     *  in: connectionInfo not null, managedConnectionInfo not null, managedConnection not null.  Handle can be null if mc is being destroyed from pool.
+     * out: managedCOnnectionInfo null, handle not in mci.handles.
+     * @param connectionInfo
+     * @param connectionReturnAction
+     */
+    public void returnConnection(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
+        if (connectionInfo.getConnectionHandle() != null) {
+            connectionInfo.getManagedConnectionInfo().removeConnectionHandle(
+                    connectionInfo);
+        }
+        next.returnConnection(connectionInfo, connectionReturnAction);
+    }
+
+    public void destroy() {
+        next.destroy();
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionInfo.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionInfo.java
new file mode 100644
index 0000000..bebca51
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionInfo.java
@@ -0,0 +1,128 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+/**
+ * ConnectionInfo.java
+ *
+ *
+ * Created: Thu Sep 25 14:29:07 2003
+ *
+ * @version 1.0
+ */
+public class ConnectionInfo {
+
+    private ManagedConnectionInfo mci;
+    private Object connection;
+    private Object connectionProxy;
+    private boolean unshareable;
+    private boolean applicationManagedSecurity;
+    private Exception trace;
+
+    public ConnectionInfo() {
+    } // ConnectionInfo constructor
+
+    public ConnectionInfo(ManagedConnectionInfo mci) {
+        this.mci = mci;
+    }
+
+    /**
+     * Get the Mci value.
+     * @return the Mci value.
+     */
+    public ManagedConnectionInfo getManagedConnectionInfo() {
+        return mci;
+    }
+
+    /**
+     * Set the Mci value.
+     * @param mci The new Mci value.
+     */
+    public void setManagedConnectionInfo(ManagedConnectionInfo mci) {
+        this.mci = mci;
+    }
+
+    /**
+     * Get the Connection value.
+     * @return the Connection value.
+     */
+    public Object getConnectionHandle() {
+        return connection;
+    }
+
+    /**
+     * Set the Connection value.
+     * @param connection The new Connection value.
+     */
+    public void setConnectionHandle(Object connection) {
+        assert this.connection == null;
+        this.connection = connection;
+    }
+
+    public Object getConnectionProxy() {
+        return connectionProxy;
+    }
+
+    public void setConnectionProxy(Object connectionProxy) {
+        this.connectionProxy = connectionProxy;
+    }
+
+    public boolean isUnshareable() {
+        return unshareable;
+    }
+
+    public void setUnshareable(boolean unshareable) {
+        this.unshareable = unshareable;
+    }
+
+    public boolean isApplicationManagedSecurity() {
+        return applicationManagedSecurity;
+    }
+
+    public void setApplicationManagedSecurity(boolean applicationManagedSecurity) {
+        this.applicationManagedSecurity = applicationManagedSecurity;
+    }
+
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof ConnectionInfo) {
+            ConnectionInfo other = (ConnectionInfo) obj;
+            return (connection == other.connection)
+                    && (mci == other.mci);
+        }
+        return false;
+    }
+
+    public int hashCode() {
+        return ((connection != null) ? connection.hashCode() : 7) ^
+                ((mci != null) ? mci.hashCode() : 7);
+    }
+
+    public void setTrace() {
+        this.trace = new Exception("Stack Trace");
+    }
+
+    public Exception getTrace() {
+        return trace;
+    }
+
+
+
+} // ConnectionInfo
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionInterceptor.java
new file mode 100644
index 0000000..99ac177
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionInterceptor.java
@@ -0,0 +1,39 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+
+/**
+ * ConnectionInterceptor is the interface implemented by
+ * ConnectionManager "aspects".  A ConnectionInterceptor
+ * implementation can provide one step of functionality for obtaining
+ * or releasing a ManagedConnection.
+ *
+ *
+ * @version $Rev$ $Date$
+ */
+
+public interface ConnectionInterceptor {
+    void getConnection(ConnectionInfo connectionInfo) throws ResourceException;
+
+    void returnConnection(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction);
+
+    void destroy();
+
+} // ConnectionInterceptor
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionInterceptorSource.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionInterceptorSource.java
new file mode 100644
index 0000000..a0b139d
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionInterceptorSource.java
@@ -0,0 +1,27 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector.outbound;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public interface ConnectionInterceptorSource {
+    ConnectionInterceptor getConnectionInterceptor();
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionManagerContainer.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionManagerContainer.java
new file mode 100644
index 0000000..cedbd16
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionManagerContainer.java
@@ -0,0 +1,34 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.ManagedConnectionFactory;
+
+/**
+ * ConnectionManagerContainer
+ *
+ * @version $Rev$ $Date$
+ */
+public interface ConnectionManagerContainer {
+
+    Object createConnectionFactory(ManagedConnectionFactory mcf) throws ResourceException;
+
+    void doRecovery(ManagedConnectionFactory managedConnectionFactory);
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionReturnAction.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionReturnAction.java
new file mode 100644
index 0000000..34f01ef
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionReturnAction.java
@@ -0,0 +1,40 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+/**
+ * ConnectionReturnAction.java
+ *
+ *
+ * Created: Thu Oct  2 15:11:39 2003
+ *
+ * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
+ * @version 1.0
+ */
+public class ConnectionReturnAction {
+
+    public final static ConnectionReturnAction RETURN_HANDLE =
+            new ConnectionReturnAction();
+    public final static ConnectionReturnAction DESTROY =
+            new ConnectionReturnAction();
+
+    private ConnectionReturnAction() {
+
+    } // ConnectionReturnAction constructor
+
+} // ConnectionReturnAction
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionTrackingInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionTrackingInterceptor.java
new file mode 100644
index 0000000..135b852
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ConnectionTrackingInterceptor.java
@@ -0,0 +1,128 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.DissociatableManagedConnection;
+import javax.resource.spi.ManagedConnection;
+
+import org.apache.geronimo.connector.outbound.connectiontracking.ConnectionTracker;
+
+/**
+ * ConnectionTrackingInterceptor.java handles communication with the
+ * CachedConnectionManager.  On method call entry, cached handles are
+ * checked for the correct Subject.  On method call exit, cached
+ * handles are disassociated if possible. On getting or releasing
+ * a connection the CachedConnectionManager is notified.
+ *
+ *
+ * @version $Rev$ $Date$
+ */
+public class ConnectionTrackingInterceptor implements ConnectionInterceptor {
+
+    private final ConnectionInterceptor next;
+    private final String key;
+    private final ConnectionTracker connectionTracker;
+
+    public ConnectionTrackingInterceptor(
+            final ConnectionInterceptor next,
+            final String key,
+            final ConnectionTracker connectionTracker
+            ) {
+        this.next = next;
+        this.key = key;
+        this.connectionTracker = connectionTracker;
+    }
+
+    /**
+     * called by: GenericConnectionManager.allocateConnection, GenericConnectionManager.associateConnection, and enter.
+     * in: connectionInfo is non-null, and has non-null ManagedConnectionInfo with non-null managedConnectionfactory.
+     * connection handle may or may not be null.
+     * out: connectionInfo has non-null connection handle, non null ManagedConnectionInfo with non-null ManagedConnection and GeronimoConnectionEventListener.
+     * connection tracker has been notified of handle-managed connection association.
+     * @param connectionInfo
+     * @throws ResourceException
+     */
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        connectionTracker.setEnvironment(connectionInfo, key);
+        next.getConnection(connectionInfo);
+        connectionTracker.handleObtained(this, connectionInfo, false);
+    }
+
+    /**
+     * Called when a proxied connection which has been released need to be reassociated with a real connection.
+     */
+    public void reassociateConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        connectionTracker.setEnvironment(connectionInfo, key);
+        next.getConnection(connectionInfo);
+        connectionTracker.handleObtained(this, connectionInfo, true);
+    }
+
+    /**
+     * called by: GeronimoConnectionEventListener.connectionClosed, GeronimoConnectionEventListener.connectionErrorOccurred, exit
+     * in: handle has already been dissociated from ManagedConnection. connectionInfo not null, has non-null ManagedConnectionInfo, ManagedConnectionInfo has non-null ManagedConnection
+     * handle can be null if called from error in ManagedConnection in pool.
+     * out: connectionTracker has been notified, ManagedConnectionInfo null.
+     * @param connectionInfo
+     * @param connectionReturnAction
+     */
+    public void returnConnection(
+            ConnectionInfo connectionInfo,
+            ConnectionReturnAction connectionReturnAction) {
+        connectionTracker.handleReleased(this, connectionInfo, connectionReturnAction);
+        next.returnConnection(connectionInfo, connectionReturnAction);
+    }
+
+    public void destroy() {
+        next.destroy();
+    }
+    
+    public void enter(Collection connectionInfos)
+            throws ResourceException {
+        for (Iterator i = connectionInfos.iterator(); i.hasNext();) {
+            ConnectionInfo connectionInfo = (ConnectionInfo) i.next();
+            next.getConnection(connectionInfo);
+        }
+
+    }
+
+    public void exit(Collection connectionInfos)
+            throws ResourceException {
+        for (Iterator i = connectionInfos.iterator(); i.hasNext();) {
+            ConnectionInfo connectionInfo = (ConnectionInfo) i.next();
+            if (connectionInfo.isUnshareable()) {
+                //if one is, they all are
+                return;
+            }
+            ManagedConnectionInfo managedConnectionInfo = connectionInfo.getManagedConnectionInfo();
+            ManagedConnection managedConnection = managedConnectionInfo.getManagedConnection();
+            if (managedConnection instanceof DissociatableManagedConnection
+                    && managedConnectionInfo.isFirstConnectionInfo(connectionInfo)) {
+                i.remove();
+                ((DissociatableManagedConnection) managedConnection).dissociateConnections();
+                managedConnectionInfo.clearConnectionHandles();
+                //todo this needs some kind of check so cx isn't returned more than once
+                //in case dissociate calls connection closed event and returns cx to pool.
+                returnConnection(connectionInfo, ConnectionReturnAction.RETURN_HANDLE);
+            }
+        }
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/GenericConnectionManager.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/GenericConnectionManager.java
new file mode 100644
index 0000000..e884238
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/GenericConnectionManager.java
@@ -0,0 +1,135 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.transaction.TransactionManager;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PartitionedPool;
+import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PoolingSupport;
+import org.apache.geronimo.connector.outbound.connectionmanagerconfig.TransactionSupport;
+import org.apache.geronimo.connector.outbound.connectiontracking.ConnectionTracker;
+import org.apache.geronimo.transaction.manager.RecoverableTransactionManager;
+
+/**
+ * GenericConnectionManager sets up a connection manager stack according to the
+ * policies described in the attributes.
+ *
+ * @version $Rev$ $Date$
+ */
+public class GenericConnectionManager extends AbstractConnectionManager {
+    protected static final Log log = LogFactory.getLog(AbstractSinglePoolConnectionInterceptor.class.getName());
+
+    //default constructor for use as endpoint
+    public GenericConnectionManager() {
+        super();
+    }
+
+    public GenericConnectionManager(TransactionSupport transactionSupport,
+                                    PoolingSupport pooling,
+                                    boolean containerManagedSecurity,
+                                    SubjectSource subjectSource,
+                                    ConnectionTracker connectionTracker,
+                                    RecoverableTransactionManager transactionManager,
+                                    String objectName,
+                                    ClassLoader classLoader) {
+        super(new InterceptorsImpl(transactionSupport, pooling, containerManagedSecurity, subjectSource, objectName, connectionTracker, transactionManager, classLoader), transactionManager);
+    }
+
+    private static class InterceptorsImpl implements AbstractConnectionManager.Interceptors {
+
+        private final ConnectionInterceptor stack;
+        private final ConnectionInterceptor recoveryStack;
+        private final PoolingSupport poolingSupport;
+
+        /**
+         * Order of constructed interceptors:
+         * <p/>
+         * ConnectionTrackingInterceptor (connectionTracker != null)
+         * TCCLInterceptor
+         * ConnectionHandleInterceptor
+         * TransactionCachingInterceptor (useTransactions & useTransactionCaching)
+         * TransactionEnlistingInterceptor (useTransactions)
+         * SubjectInterceptor (realmBridge != null)
+         * SinglePoolConnectionInterceptor or MultiPoolConnectionInterceptor
+         * LocalXAResourceInsertionInterceptor or XAResourceInsertionInterceptor (useTransactions (&localTransactions))
+         * MCFConnectionInterceptor
+         */
+        public InterceptorsImpl(TransactionSupport transactionSupport,
+                                PoolingSupport pooling,
+                                boolean containerManagedSecurity,
+                                SubjectSource subjectSource, String objectName,
+                                ConnectionTracker connectionTracker,
+                                TransactionManager transactionManager,
+                                ClassLoader classLoader) {
+            //check for consistency between attributes
+            if (!containerManagedSecurity && pooling instanceof PartitionedPool && ((PartitionedPool) pooling).isPartitionBySubject()) {
+                throw new IllegalStateException("To use Subject in pooling, you need a SecurityDomain");
+            }
+
+            //Set up the interceptor stack
+            MCFConnectionInterceptor tail = new MCFConnectionInterceptor();
+            ConnectionInterceptor stack = tail;
+
+            stack = transactionSupport.addXAResourceInsertionInterceptor(stack, objectName);
+            stack = pooling.addPoolingInterceptors(stack);
+            if (log.isTraceEnabled()) {
+                log.trace("Connection Manager " + objectName + " installed pool " + stack);
+            }
+
+            this.poolingSupport = pooling;
+            stack = transactionSupport.addTransactionInterceptors(stack, transactionManager);
+
+            if (containerManagedSecurity) {
+                stack = new SubjectInterceptor(stack, subjectSource);
+            }
+
+            if (transactionSupport.isRecoverable()) {
+        	this.recoveryStack = new TCCLInterceptor(stack, classLoader);
+            } else {
+        	this.recoveryStack = null;
+            }
+            
+
+            stack = new ConnectionHandleInterceptor(stack);
+            stack = new TCCLInterceptor(stack, classLoader);
+            if (connectionTracker != null) {
+                stack = new ConnectionTrackingInterceptor(stack,
+                        objectName,
+                        connectionTracker);
+            }
+            tail.setStack(stack);
+            this.stack = stack;
+        }
+
+        public ConnectionInterceptor getStack() {
+            return stack;
+        }
+
+        public ConnectionInterceptor getRecoveryStack() {
+            return recoveryStack;
+        }
+        
+        public PoolingSupport getPoolingAttributes() {
+            return poolingSupport;
+        }
+
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/GeronimoConnectionEventListener.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/GeronimoConnectionEventListener.java
new file mode 100644
index 0000000..5e2d117
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/GeronimoConnectionEventListener.java
@@ -0,0 +1,150 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import javax.resource.spi.ConnectionEvent;
+import javax.resource.spi.ConnectionEventListener;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * ConnectionEventListener.java
+ *
+ *
+ * Created: Thu Oct  2 14:57:43 2003
+ *
+ * @version 1.0
+ */
+public class GeronimoConnectionEventListener implements ConnectionEventListener {
+
+    private static Log log = LogFactory.getLog(GeronimoConnectionEventListener.class.getName());
+
+    private final ManagedConnectionInfo managedConnectionInfo;
+    private final ConnectionInterceptor stack;
+    private final List connectionInfos = new ArrayList();
+    private boolean errorOccurred = false;
+
+    public GeronimoConnectionEventListener(
+            final ConnectionInterceptor stack,
+            final ManagedConnectionInfo managedConnectionInfo) {
+        this.stack = stack;
+        this.managedConnectionInfo = managedConnectionInfo;
+    }
+
+    public void connectionClosed(ConnectionEvent connectionEvent) {
+        if (connectionEvent.getSource() != managedConnectionInfo.getManagedConnection()) {
+            throw new IllegalArgumentException(
+                    "ConnectionClosed event received from wrong ManagedConnection. Expected "
+                    + managedConnectionInfo.getManagedConnection()
+                    + ", actual "
+                    + connectionEvent.getSource());
+        }
+        if (log.isTraceEnabled()) {
+            log.trace("connectionClosed called with " + connectionEvent.getConnectionHandle() + " for MCI: " + managedConnectionInfo + " and MC: " + managedConnectionInfo.getManagedConnection());
+        }
+        ConnectionInfo ci = new ConnectionInfo(managedConnectionInfo);
+        ci.setConnectionHandle(connectionEvent.getConnectionHandle());
+        try {
+            stack.returnConnection(ci, ConnectionReturnAction.RETURN_HANDLE);
+        } catch (Throwable e) {
+            if (log.isTraceEnabled()) {
+                log.trace("connectionClosed failed with " + connectionEvent.getConnectionHandle() + " for MCI: " + managedConnectionInfo + " and MC: " + managedConnectionInfo.getManagedConnection(), e);
+            }
+            if (e instanceof Error) {
+                throw (Error)e;
+            }
+        }
+    }
+
+    public void connectionErrorOccurred(ConnectionEvent connectionEvent) {
+        if (connectionEvent.getSource() != managedConnectionInfo.getManagedConnection()) {
+            throw new IllegalArgumentException(
+                    "ConnectionError event received from wrong ManagedConnection. Expected "
+                    + managedConnectionInfo.getManagedConnection()
+                    + ", actual "
+                    + connectionEvent.getSource());
+        }
+        log.warn("connectionErrorOccurred called with " + connectionEvent.getConnectionHandle(), connectionEvent.getException());
+        boolean errorOccurred = this.errorOccurred;
+        this.errorOccurred = true;
+        if (!errorOccurred) {
+            ConnectionInfo ci = new ConnectionInfo(managedConnectionInfo);
+            ci.setConnectionHandle(connectionEvent.getConnectionHandle());
+            stack.returnConnection(ci, ConnectionReturnAction.DESTROY);
+        }
+    }
+
+    public void localTransactionStarted(ConnectionEvent event) {
+        //TODO implement this method
+    }
+
+    /**
+     * The <code>localTransactionCommitted</code> method
+     *
+     * @param event a <code>ConnectionEvent</code> value
+     * todo implement this method
+     */
+    public void localTransactionCommitted(ConnectionEvent event) {
+    }
+
+    /**
+     * The <code>localTransactionRolledback</code> method
+     *
+     * @param event a <code>ConnectionEvent</code> value
+     * todo implement this method
+     */
+    public void localTransactionRolledback(ConnectionEvent event) {
+    }
+
+    public void addConnectionInfo(ConnectionInfo connectionInfo) {
+        assert connectionInfo.getConnectionHandle() != null;
+        connectionInfos.add(connectionInfo);
+    }
+
+    public void removeConnectionInfo(ConnectionInfo connectionInfo) {
+        assert connectionInfo.getConnectionHandle() != null;
+        connectionInfos.remove(connectionInfo);
+    }
+
+    public boolean hasConnectionInfos() {
+        return !connectionInfos.isEmpty();
+    }
+
+    public void clearConnectionInfos() {
+        connectionInfos.clear();
+    }
+
+    public boolean hasConnectionInfo(ConnectionInfo connectionInfo) {
+        return connectionInfos.contains(connectionInfo);
+    }
+
+    public boolean isFirstConnectionInfo(ConnectionInfo connectionInfo) {
+        return !connectionInfos.isEmpty() && connectionInfos.get(0) == connectionInfo;
+    }
+
+    public Collection getConnectionInfos() {
+        return Collections.unmodifiableCollection(connectionInfos);
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/LocalXAResource.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/LocalXAResource.java
new file mode 100644
index 0000000..a8f7d4b
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/LocalXAResource.java
@@ -0,0 +1,135 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.LocalTransaction;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import org.apache.geronimo.transaction.manager.NamedXAResource;
+
+/**
+ * LocalXAResource adapts a local transaction to be controlled by a
+ * JTA transaction manager.  Of course, it cannot provide xa
+ * semantics.
+ *
+ *
+ * @version $Rev$ $Date$
+ */
+public class LocalXAResource implements NamedXAResource {
+
+    //accessible in package for testing
+    final LocalTransaction localTransaction;
+    private final String name;
+    private Xid xid;
+    private int transactionTimeout;
+
+    public LocalXAResource(LocalTransaction localTransaction, String name) {
+        this.localTransaction = localTransaction;
+        this.name = name;
+    }
+
+    // Implementation of javax.transaction.xa.XAResource
+
+    public void commit(Xid xid, boolean flag) throws XAException {
+        if (this.xid == null || !this.xid.equals(xid)) {
+            throw new XAException("Invalid Xid");
+        }
+        try {
+            localTransaction.commit();
+        } catch (ResourceException e) {
+            throw (XAException)new XAException().initCause(e);
+         } finally {
+            this.xid = null;
+        }
+
+    }
+
+    public void forget(Xid xid) throws XAException {
+        this.xid = null;
+    }
+
+    public int getTransactionTimeout() throws XAException {
+        return transactionTimeout;
+    }
+
+    public boolean isSameRM(XAResource xares) throws XAException {
+        return this == xares;
+    }
+
+    public Xid[] recover(int n) throws XAException {
+        return new Xid[0];
+    }
+
+    public void rollback(Xid xid) throws XAException {
+        if (this.xid == null || !this.xid.equals(xid)) {
+            throw new XAException("Invalid Xid");
+        }
+        try {
+            localTransaction.rollback();
+        } catch (ResourceException e) {
+            throw (XAException)new XAException().initCause(e);
+        } finally {
+            this.xid = null;
+        }
+    }
+
+    public boolean setTransactionTimeout(int txTimeout) throws XAException {
+        this.transactionTimeout = txTimeout;
+        return true;
+    }
+
+    public void start(Xid xid, int flag) throws XAException {
+        if (flag == XAResource.TMNOFLAGS) {
+            // first time in this transaction
+            if (this.xid != null) {
+                throw new XAException("already enlisted");
+            }
+            this.xid = xid;
+            try {
+                localTransaction.begin();
+            } catch (ResourceException e) {
+                throw (XAException) new XAException("could not start local tx").initCause(e);
+            }
+        } else if (flag == XAResource.TMRESUME) {
+            if (xid != this.xid) {
+                throw new XAException("attempting to resume in different transaction");
+            }
+        } else {
+            throw new XAException("unknown state");
+        }
+    }
+
+    public void end(Xid xid, int flag) throws XAException {
+        if (xid != this.xid) {
+            throw new XAException("Invalid Xid");
+        }
+        //we could keep track of if the flag is TMSUCCESS...
+    }
+
+    public int prepare(Xid xid) throws XAException {
+        //log warning that semantics are incorrect...
+        return XAResource.XA_OK;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/LocalXAResourceInsertionInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/LocalXAResourceInsertionInterceptor.java
new file mode 100644
index 0000000..6abc484
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/LocalXAResourceInsertionInterceptor.java
@@ -0,0 +1,56 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+
+/**
+ * LocalXAResourceInsertionInterceptor.java
+ *
+ *
+ * @version $Rev$ $Date$
+
+ */
+public class LocalXAResourceInsertionInterceptor
+        implements ConnectionInterceptor {
+
+    private final ConnectionInterceptor next;
+    private final String name;
+
+    public LocalXAResourceInsertionInterceptor(final ConnectionInterceptor next, final String name) {
+        this.next = next;
+        this.name = name;
+    }
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        next.getConnection(connectionInfo);
+        ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+        mci.setXAResource(
+                new LocalXAResource(mci.getManagedConnection().getLocalTransaction(), name));
+    }
+
+    public void returnConnection(
+            ConnectionInfo connectionInfo,
+            ConnectionReturnAction connectionReturnAction) {
+        next.returnConnection(connectionInfo, connectionReturnAction);
+    }
+
+    public void destroy() {
+        next.destroy();        
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/MCFConnectionInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/MCFConnectionInterceptor.java
new file mode 100644
index 0000000..17623dd
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/MCFConnectionInterceptor.java
@@ -0,0 +1,86 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.ManagedConnection;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * MCFConnectionInterceptor.java
+ *
+ *
+ * @version $Rev$ $Date$
+ */
+public class MCFConnectionInterceptor implements ConnectionInterceptor {
+
+    protected static final Log log = LogFactory.getLog(MCFConnectionInterceptor.class.getName());
+
+    private ConnectionInterceptor stack;
+
+    public MCFConnectionInterceptor() {
+    }
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+        if (mci.getManagedConnection() != null) {
+            return;
+        }
+        
+        try {
+            ManagedConnection mc =
+                mci.getManagedConnectionFactory().createManagedConnection(
+                        mci.getSubject(),
+                        mci.getConnectionRequestInfo());
+            mci.setManagedConnection(mc);
+            GeronimoConnectionEventListener listener = new GeronimoConnectionEventListener(stack, mci);
+            mci.setConnectionEventListener(listener);
+            mc.addConnectionEventListener(listener);
+        } catch (ResourceException re) {
+            log.error("Error occurred creating ManagedConnection for " + connectionInfo, re);
+            throw re;
+        }
+    }
+
+    public void returnConnection(
+            ConnectionInfo connectionInfo,
+            ConnectionReturnAction connectionReturnAction) {
+        ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+        ManagedConnection mc = mci.getManagedConnection();
+        try {
+            mc.destroy();
+        } catch (ResourceException e) {
+            //log and forget
+        } catch (Error e) {
+            throw e;
+        } catch (Throwable t) {
+            //log and forget
+        }
+    }
+
+    public void destroy() {
+        // MCF is the "tail" of the stack. So, we're all done...
+    }
+    
+    public void setStack(ConnectionInterceptor stack) {
+        this.stack = stack;
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ManagedConnectionInfo.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ManagedConnectionInfo.java
new file mode 100644
index 0000000..c2a7937
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ManagedConnectionInfo.java
@@ -0,0 +1,154 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import java.util.Collection;
+
+import javax.resource.spi.ConnectionRequestInfo;
+import javax.resource.spi.ManagedConnection;
+import javax.resource.spi.ManagedConnectionFactory;
+import javax.security.auth.Subject;
+import javax.transaction.xa.XAResource;
+
+/**
+ * ConnectionRequest.java
+ *
+ *
+ * Created: Thu Sep 25 14:29:07 2003
+ *
+ * @version 1.0
+ */
+public class ManagedConnectionInfo {
+
+    private ManagedConnectionFactory managedConnectionFactory;
+    private ConnectionRequestInfo connectionRequestInfo;
+    private Subject subject;
+    private ManagedConnection managedConnection;
+    private XAResource xares;
+    private long lastUsed;
+    private ConnectionInterceptor poolInterceptor;
+
+    private GeronimoConnectionEventListener listener;
+
+    public ManagedConnectionInfo(
+            ManagedConnectionFactory managedConnectionFactory,
+            ConnectionRequestInfo connectionRequestInfo) {
+        this.managedConnectionFactory = managedConnectionFactory;
+        this.connectionRequestInfo = connectionRequestInfo;
+    }
+
+    public ManagedConnectionFactory getManagedConnectionFactory() {
+        return managedConnectionFactory;
+    }
+
+    public void setManagedConnectionFactory(ManagedConnectionFactory managedConnectionFactory) {
+        this.managedConnectionFactory = managedConnectionFactory;
+    }
+
+    public ConnectionRequestInfo getConnectionRequestInfo() {
+        return connectionRequestInfo;
+    }
+
+    public void setConnectionRequestInfo(ConnectionRequestInfo cri) {
+        this.connectionRequestInfo = cri;
+    }
+
+    public Subject getSubject() {
+        return subject;
+    }
+
+    public void setSubject(Subject subject) {
+        this.subject = subject;
+    }
+
+    public ManagedConnection getManagedConnection() {
+        return managedConnection;
+    }
+
+    public void setManagedConnection(ManagedConnection managedConnection) {
+        assert this.managedConnection == null;
+        this.managedConnection = managedConnection;
+    }
+
+    public XAResource getXAResource() {
+        return xares;
+    }
+
+    public void setXAResource(XAResource xares) {
+        this.xares = xares;
+    }
+
+    public long getLastUsed() {
+        return lastUsed;
+    }
+
+    public void setLastUsed(long lastUsed) {
+        this.lastUsed = lastUsed;
+    }
+
+    public void setPoolInterceptor(ConnectionInterceptor poolInterceptor) {
+        this.poolInterceptor = poolInterceptor;
+    }
+
+    public ConnectionInterceptor getPoolInterceptor() {
+        return poolInterceptor;
+    }
+
+    public void setConnectionEventListener(GeronimoConnectionEventListener listener) {
+        this.listener = listener;
+    }
+
+    public void addConnectionHandle(ConnectionInfo connectionInfo) {
+        listener.addConnectionInfo(connectionInfo);
+    }
+
+    public void removeConnectionHandle(ConnectionInfo connectionInfo) {
+        listener.removeConnectionInfo(connectionInfo);
+    }
+
+    public boolean hasConnectionHandles() {
+        return listener.hasConnectionInfos();
+    }
+
+    public void clearConnectionHandles() {
+        listener.clearConnectionInfos();
+    }
+
+    public Collection getConnectionInfos() {
+        return listener.getConnectionInfos();
+    }
+
+    public boolean securityMatches(ManagedConnectionInfo other) {
+        return (
+                subject == null
+                ? other.getSubject() == null
+                : subject.equals(other.getSubject()))
+                && (connectionRequestInfo == null
+                ? other.getConnectionRequestInfo() == null
+                : connectionRequestInfo.equals(other.getConnectionRequestInfo()));
+    }
+
+    public boolean hasConnectionInfo(ConnectionInfo connectionInfo) {
+        return listener.hasConnectionInfo(connectionInfo);
+    }
+
+    public boolean isFirstConnectionInfo(ConnectionInfo connectionInfo) {
+        return listener.isFirstConnectionInfo(connectionInfo);
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/MultiPoolConnectionInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/MultiPoolConnectionInterceptor.java
new file mode 100644
index 0000000..c93acf9
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/MultiPoolConnectionInterceptor.java
@@ -0,0 +1,208 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.ConnectionRequestInfo;
+import javax.security.auth.Subject;
+
+import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PoolingSupport;
+
+/**
+ * MultiPoolConnectionInterceptor maps the provided subject and connection request info to a
+ * "SinglePool".  This can be used to make sure all matches will succeed, avoiding synchronization
+ * slowdowns.
+ *
+ * Created: Fri Oct 10 12:53:11 2003
+ *
+ * @version $Rev$ $Date$
+ */
+public class MultiPoolConnectionInterceptor implements ConnectionInterceptor, PoolingAttributes{
+
+    private final ConnectionInterceptor next;
+    private final PoolingSupport singlePoolFactory;
+
+    private final boolean useSubject;
+
+    private final boolean useCRI;
+
+    private final Map pools = new HashMap();
+
+    // volatile is not necessary, here, because of synchronization. but maintained for consistency with other Interceptors...
+    private volatile boolean destroyed = false;
+
+    public MultiPoolConnectionInterceptor(
+            final ConnectionInterceptor next,
+            PoolingSupport singlePoolFactory,
+            final boolean useSubject,
+            final boolean useCRI) {
+        this.next = next;
+        this.singlePoolFactory = singlePoolFactory;
+        this.useSubject = useSubject;
+        this.useCRI = useCRI;
+    }
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+        SubjectCRIKey key =
+                new SubjectCRIKey(
+                        useSubject ? mci.getSubject() : null,
+                        useCRI ? mci.getConnectionRequestInfo() : null);
+        ConnectionInterceptor poolInterceptor = null;
+        synchronized (pools) {
+            if (destroyed) {
+                throw new ResourceException("ConnectionManaged has been destroyed");
+            }
+            poolInterceptor = (ConnectionInterceptor) pools.get(key);
+            if (poolInterceptor == null) {
+                poolInterceptor = singlePoolFactory.addPoolingInterceptors(next);
+                pools.put(key, poolInterceptor);
+            }
+        }
+        mci.setPoolInterceptor(poolInterceptor);
+        poolInterceptor.getConnection(connectionInfo);
+    }
+
+    // let underlying pools handle destroyed processing...
+    public void returnConnection(
+            ConnectionInfo connectionInfo,
+            ConnectionReturnAction connectionReturnAction) {
+        ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+        ConnectionInterceptor poolInterceptor = mci.getPoolInterceptor();
+        poolInterceptor.returnConnection(connectionInfo, connectionReturnAction);
+    }
+
+    public void destroy() {
+        synchronized (pools) {
+            destroyed = true;
+            for (Iterator it = pools.entrySet().iterator(); it.hasNext(); ) {
+                ((ConnectionInterceptor)((Map.Entry)it.next()).getValue()).destroy();
+                it.remove();
+            }
+        }
+        next.destroy();
+    }
+    
+    public int getPartitionCount() {
+        return pools.size();
+    }
+
+    public int getPartitionMaxSize() {
+        return singlePoolFactory.getPartitionMaxSize();
+    }
+
+    public void setPartitionMaxSize(int maxSize) throws InterruptedException {
+        singlePoolFactory.setPartitionMaxSize(maxSize);
+        for (Iterator iterator = pools.entrySet().iterator(); iterator.hasNext();) {
+            PoolingAttributes poolingAttributes = (PoolingAttributes) ((Map.Entry) iterator.next()).getValue();
+            poolingAttributes.setPartitionMaxSize(maxSize);
+        }
+    }
+
+    public int getPartitionMinSize() {
+        return singlePoolFactory.getPartitionMinSize();
+    }
+
+    public void setPartitionMinSize(int minSize) {
+        singlePoolFactory.setPartitionMinSize(minSize);
+        for (Iterator iterator = pools.entrySet().iterator(); iterator.hasNext();) {
+            PoolingAttributes poolingAttributes = (PoolingAttributes) ((Map.Entry) iterator.next()).getValue();
+            poolingAttributes.setPartitionMinSize(minSize);
+        }
+    }
+
+    public int getIdleConnectionCount() {
+        int count = 0;
+        for (Iterator iterator = pools.entrySet().iterator(); iterator.hasNext();) {
+            PoolingAttributes poolingAttributes = (PoolingAttributes) ((Map.Entry) iterator.next()).getValue();
+            count += poolingAttributes.getIdleConnectionCount();
+        }
+        return count;
+    }
+
+    public int getConnectionCount() {
+        int count = 0;
+        for (Iterator iterator = pools.entrySet().iterator(); iterator.hasNext();) {
+            PoolingAttributes poolingAttributes = (PoolingAttributes) ((Map.Entry) iterator.next()).getValue();
+            count += poolingAttributes.getConnectionCount();
+        }
+        return count;
+    }
+
+    public int getBlockingTimeoutMilliseconds() {
+        return singlePoolFactory.getBlockingTimeoutMilliseconds();
+    }
+
+    public void setBlockingTimeoutMilliseconds(int timeoutMilliseconds) {
+        singlePoolFactory.setBlockingTimeoutMilliseconds(timeoutMilliseconds);
+        for (Iterator iterator = pools.entrySet().iterator(); iterator.hasNext();) {
+            PoolingAttributes poolingAttributes = (PoolingAttributes) ((Map.Entry) iterator.next()).getValue();
+            poolingAttributes.setBlockingTimeoutMilliseconds(timeoutMilliseconds);
+        }
+    }
+
+    public int getIdleTimeoutMinutes() {
+        return singlePoolFactory.getIdleTimeoutMinutes();
+    }
+
+    public void setIdleTimeoutMinutes(int idleTimeoutMinutes) {
+        singlePoolFactory.setIdleTimeoutMinutes(idleTimeoutMinutes);
+        for (Iterator iterator = pools.entrySet().iterator(); iterator.hasNext();) {
+            PoolingAttributes poolingAttributes = (PoolingAttributes) ((Map.Entry) iterator.next()).getValue();
+            poolingAttributes.setIdleTimeoutMinutes(idleTimeoutMinutes);
+        }
+    }
+
+    static class SubjectCRIKey {
+        private final Subject subject;
+        private final ConnectionRequestInfo cri;
+        private final int hashcode;
+
+        public SubjectCRIKey(
+                final Subject subject,
+                final ConnectionRequestInfo cri) {
+            this.subject = subject;
+            this.cri = cri;
+            this.hashcode =
+                    (subject == null ? 17 : subject.hashCode() * 17)
+                    ^ (cri == null ? 1 : cri.hashCode());
+        }
+
+        public int hashCode() {
+            return hashcode;
+        }
+
+        public boolean equals(Object other) {
+            if (!(other instanceof SubjectCRIKey)) {
+                return false;
+            } // end of if ()
+            SubjectCRIKey o = (SubjectCRIKey) other;
+            if (hashcode != o.hashcode) {
+                return false;
+            } // end of if ()
+            return subject == null
+                    ? o.subject == null
+                    : subject.equals(o.subject)
+                    && cri == null ? o.cri == null : cri.equals(o.cri);
+        }
+    }
+} // MultiPoolConnectionInterceptor
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/PoolIdleReleaserTimer.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/PoolIdleReleaserTimer.java
new file mode 100644
index 0000000..f4586bc
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/PoolIdleReleaserTimer.java
@@ -0,0 +1,31 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector.outbound;
+
+import java.util.Timer;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class PoolIdleReleaserTimer {
+
+    private static final Timer timer = new Timer(true);
+
+    public static Timer getTimer() {
+        return timer;
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/PoolingAttributes.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/PoolingAttributes.java
new file mode 100644
index 0000000..c6aa9ec
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/PoolingAttributes.java
@@ -0,0 +1,45 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public interface PoolingAttributes {
+    int getPartitionCount();
+
+    int getConnectionCount();
+
+    int getIdleConnectionCount();
+
+    int getPartitionMaxSize();
+
+    void setPartitionMaxSize(int maxSize) throws InterruptedException;
+
+    int getPartitionMinSize();
+
+    void setPartitionMinSize(int minSize);
+
+    int getBlockingTimeoutMilliseconds();
+
+    void setBlockingTimeoutMilliseconds(int timeoutMilliseconds);
+
+    int getIdleTimeoutMinutes();
+
+    void setIdleTimeoutMinutes(int idleTimeoutMinutes);
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/SinglePoolConnectionInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/SinglePoolConnectionInterceptor.java
new file mode 100644
index 0000000..3efa0e7
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/SinglePoolConnectionInterceptor.java
@@ -0,0 +1,286 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.ManagedConnection;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * SinglePoolConnectionInterceptor chooses a single connection from the pool.  If selectOneAssumeMatch
+ * is true, it simply returns the selected connection.
+ * THIS SHOULD BE USED ONLY IF MAXIMUM SPEED IS ESSENTIAL AND YOU HAVE THOROUGLY CHECKED THAT
+ * MATCHING WOULD SUCCEED ON THE SELECTED CONNECTION. (i.e., read the docs on your connector
+ * to find out how matching works)
+ * If selectOneAssumeMatch is false, it checks with the ManagedConnectionFactory that the
+ * selected connection does match before returning it: if not it throws an exception.
+ *
+ * @version $Rev$ $Date$
+ */
+public class SinglePoolConnectionInterceptor extends AbstractSinglePoolConnectionInterceptor {
+    private static final Log log = LogFactory.getLog(SinglePoolConnectionInterceptor.class.getName());
+
+    private boolean selectOneAssumeMatch;
+
+    private PoolDeque pool;
+
+    public SinglePoolConnectionInterceptor(final ConnectionInterceptor next,
+                                           int maxSize,
+                                           int minSize,
+                                           int blockingTimeoutMilliseconds,
+                                           int idleTimeoutMinutes,
+                                           boolean selectOneAssumeMatch) {
+        super(next, maxSize, minSize, blockingTimeoutMilliseconds, idleTimeoutMinutes);
+        pool = new PoolDeque(maxSize);
+        this.selectOneAssumeMatch = selectOneAssumeMatch;
+    }
+
+    protected void internalGetConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        synchronized (pool) {
+            if (destroyed) {
+                throw new ResourceException("ManagedConnection pool has been destroyed");
+            }
+
+            ManagedConnectionInfo newMCI = null;
+            if (pool.isEmpty()) {
+                next.getConnection(connectionInfo);
+                connectionCount++;
+                if (log.isTraceEnabled()) {
+                    log.trace("Supplying new connection MCI: " + connectionInfo.getManagedConnectionInfo() + " MC: " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " from pool: " + this);
+                }
+                return;
+            } else {
+                newMCI = pool.removeLast();
+            }
+            if (connectionCount < minSize) {
+                timer.schedule(new FillTask(connectionInfo), 10);
+            }
+            if (selectOneAssumeMatch) {
+                connectionInfo.setManagedConnectionInfo(newMCI);
+                if (log.isTraceEnabled()) {
+                    log.trace("Supplying pooled connection without checking matching MCI: " + connectionInfo.getManagedConnectionInfo() + " MC: " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " from pool: " + this);
+                }
+                return;
+            }
+            try {
+                ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+                ManagedConnection matchedMC =
+                        newMCI
+                        .getManagedConnectionFactory()
+                        .matchManagedConnections(Collections.singleton(newMCI.getManagedConnection()),
+                                mci.getSubject(),
+                                mci.getConnectionRequestInfo());
+                if (matchedMC != null) {
+                    connectionInfo.setManagedConnectionInfo(newMCI);
+                    if (log.isTraceEnabled()) {
+                        log.trace("Supplying pooled connection  MCI: " + connectionInfo.getManagedConnectionInfo() + " MC: " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " from pool: " + this);
+                    }
+                    return;
+                } else {
+                    //matching failed.
+                    ConnectionInfo returnCI = new ConnectionInfo();
+                    returnCI.setManagedConnectionInfo(newMCI);
+                    returnConnection(returnCI,
+                            ConnectionReturnAction.RETURN_HANDLE);
+                    throw new ResourceException("The pooling strategy does not match the MatchManagedConnections implementation.  Please investigate and reconfigure this pool");
+                }
+            } catch (ResourceException e) {
+                //something is wrong: destroy connection, rethrow, release permit
+                ConnectionInfo returnCI = new ConnectionInfo();
+                returnCI.setManagedConnectionInfo(newMCI);
+                returnConnection(returnCI,
+                        ConnectionReturnAction.DESTROY);
+                throw e;
+            }
+        }
+    }
+
+    protected void internalDestroy() {
+        synchronized (pool) {
+            while (!pool.isEmpty()) {
+                ManagedConnection mc = pool.removeLast().getManagedConnection();
+                if (mc != null) {
+                    try {
+                        mc.destroy();
+                    }
+                    catch (ResourceException re) { } // ignore
+                }
+            }
+        }
+    }
+
+    protected boolean internalReturn(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
+        ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+        ManagedConnection mc = mci.getManagedConnection();
+        if (connectionReturnAction == ConnectionReturnAction.RETURN_HANDLE) {
+            try {
+                mc.cleanup();
+            } catch (ResourceException e) {
+                connectionReturnAction = ConnectionReturnAction.DESTROY;
+            }
+        }
+        boolean wasInPool = false;
+        synchronized (pool) {
+            // a bit redundant with returnConnection check in AbstractSinglePoolConnectionInterceptor, 
+            // but checking here closes a small timing hole...
+            if (destroyed) {
+                try {
+                    mc.destroy();
+                }
+                catch (ResourceException re) { } // ignore
+                return pool.remove(mci);
+            }
+
+            if (shrinkLater > 0) {
+                //nothing can get in the pool while shrinkLater > 0, so wasInPool is false here.
+                connectionReturnAction = ConnectionReturnAction.DESTROY;
+                shrinkLater--;
+            } else if (connectionReturnAction == ConnectionReturnAction.RETURN_HANDLE) {
+                mci.setLastUsed(System.currentTimeMillis());
+                pool.add(mci);
+                return wasInPool;
+            } else {
+                wasInPool = pool.remove(mci);
+            }
+        }
+        //we must destroy connection.
+        next.returnConnection(connectionInfo, connectionReturnAction);
+        connectionCount--;
+        return wasInPool;
+    }
+
+    public int getPartitionMaxSize() {
+        return pool.capacity();
+    }
+
+    protected void transferConnections(int maxSize, int shrinkNow) {
+        //1st example: copy 0 (none)
+        //2nd example: copy 10 (all)
+        PoolDeque oldPool = pool;
+        pool = new PoolDeque(maxSize);
+        //since we have replaced pool already, pool.remove will be very fast:-)
+        for (int i = 0; i < shrinkNow; i++) {
+            ConnectionInfo killInfo = new ConnectionInfo(oldPool.peek(i));
+            internalReturn(killInfo, ConnectionReturnAction.DESTROY);
+        }
+        for (int i = shrinkNow; i < connectionCount; i++) {
+            pool.add(oldPool.peek(i));
+        }
+    }
+
+    public int getIdleConnectionCount() {
+        return pool.currentSize();
+    }
+
+
+    protected void getExpiredManagedConnectionInfos(long threshold, ArrayList killList) {
+        synchronized (pool) {
+            for (int i = 0; i < pool.currentSize(); i++) {
+                ManagedConnectionInfo mci = pool.peek(i);
+                if (mci.getLastUsed() < threshold) {
+                    killList.add(mci);
+                }
+            }
+        }
+    }
+
+    protected boolean addToPool(ManagedConnectionInfo mci) {
+        boolean added;
+        synchronized (pool) {
+            connectionCount++;
+            added = getPartitionMaxSize() > getIdleConnectionCount();
+            if (added) {
+                pool.add(mci);
+            }
+        }
+        return added;
+    }
+
+    static class PoolDeque {
+
+        private final ManagedConnectionInfo[] deque;
+        private final int first = 0;
+        private int last = -1;
+
+        public PoolDeque(int size) {
+            deque = new ManagedConnectionInfo[size];
+        }
+
+        //internal
+        public boolean isEmpty() {
+            return first > last;
+        }
+
+        //internal
+        public void add(ManagedConnectionInfo mci) {
+            if (last == deque.length - 1) {
+                throw new IllegalStateException("deque is full: contents: " + Arrays.asList(deque));
+            }
+            deque[++last] = mci;
+        }
+
+        //internal
+        public ManagedConnectionInfo peek(int i) {
+            if (i < first || i > last) {
+                throw new IllegalStateException("index is out of current range");
+            }
+            return deque[i];
+        }
+
+        //internal
+        public ManagedConnectionInfo removeLast() {
+            if (isEmpty()) {
+                throw new IllegalStateException("deque is empty");
+            }
+
+            return deque[last--];
+        }
+
+        //internal
+        public boolean remove(ManagedConnectionInfo mci) {
+            for (int i = first; i <= last; i++) {
+                if (deque[i] == mci) {
+                    for (int j = i + 1; j <= last; j++) {
+                        deque[j - 1] = deque[j];
+                    }
+                    last--;
+                    return true;
+                }
+
+            }
+            return false;
+        }
+
+        //internal
+        public int capacity() {
+            return deque.length;
+        }
+
+        //internal
+        public int currentSize() {
+            return last - first + 1;
+        }
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/SinglePoolMatchAllConnectionInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/SinglePoolMatchAllConnectionInterceptor.java
new file mode 100644
index 0000000..6eac273
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/SinglePoolMatchAllConnectionInterceptor.java
@@ -0,0 +1,209 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.ManagedConnection;
+import javax.resource.spi.ManagedConnectionFactory;
+
+/**
+ * This pool is the most spec-compliant pool.  It can be used by itself with no partitioning.
+ * It is apt to be the slowest pool.
+ * For each connection request, it synchronizes access to the pool and asks the
+ * ManagedConnectionFactory for a match from among all managed connections.  If none is found,
+ * it may discard a random existing connection, and creates a new connection.
+ *
+ * @version $Rev$ $Date$
+ */
+public class SinglePoolMatchAllConnectionInterceptor extends AbstractSinglePoolConnectionInterceptor {
+
+    private HashMap pool;
+
+    private int maxSize;
+
+    public SinglePoolMatchAllConnectionInterceptor(final ConnectionInterceptor next,
+                                                   int maxSize,
+                                                   int minSize,
+                                                   int blockingTimeoutMilliseconds,
+                                                   int idleTimeoutMinutes) {
+
+        super(next, maxSize, minSize, blockingTimeoutMilliseconds, idleTimeoutMinutes);
+        this.maxSize = maxSize;
+        pool = new HashMap(maxSize);
+    }
+
+    protected void internalGetConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        synchronized (pool) {
+            if (destroyed) {
+                throw new ResourceException("ManagedConnection pool has been destroyed");
+            }
+            try {
+                if (!pool.isEmpty()) {
+                    ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+                    ManagedConnectionFactory managedConnectionFactory = mci.getManagedConnectionFactory();
+                    ManagedConnection matchedMC =
+                            managedConnectionFactory
+                            .matchManagedConnections(pool.keySet(),
+                                    mci.getSubject(),
+                                    mci.getConnectionRequestInfo());
+                    if (matchedMC != null) {
+                        connectionInfo.setManagedConnectionInfo((ManagedConnectionInfo) pool.get(matchedMC));
+                        pool.remove(matchedMC);
+                        if (log.isTraceEnabled()) {
+                            log.trace("Returning pooled connection " + connectionInfo.getManagedConnectionInfo());
+                        }
+                        if (connectionCount < minSize) {
+                            timer.schedule(new FillTask(connectionInfo), 10);
+                        }
+                        return;
+                    }
+                }
+                //matching failed or pool is empty
+                //if pool is at maximum size, pick a cx to kill
+                if (connectionCount == maxSize) {
+                    Iterator iterator = pool.entrySet().iterator();
+                    ManagedConnectionInfo kill = (ManagedConnectionInfo) ((Map.Entry) iterator.next()).getValue();
+                    iterator.remove();
+                    ConnectionInfo killInfo = new ConnectionInfo(kill);
+                    internalReturn(killInfo, ConnectionReturnAction.DESTROY);
+                }
+                next.getConnection(connectionInfo);
+                connectionCount++;
+                if (log.isTraceEnabled()) {
+                    log.trace("Returning new connection " + connectionInfo.getManagedConnectionInfo());
+                }
+                if (connectionCount < minSize) {
+                    timer.schedule(new FillTask(connectionInfo), 10);
+                }
+
+            } catch (ResourceException e) {
+                //something is wrong: rethrow, release permit
+                permits.release();
+                throw e;
+            }
+        }
+    }
+
+    protected boolean internalReturn(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
+        ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+        ManagedConnection mc = mci.getManagedConnection();
+        try {
+            mc.cleanup();
+        } catch (ResourceException e) {
+            connectionReturnAction = ConnectionReturnAction.DESTROY;
+        }
+
+        boolean wasInPool = false;
+        synchronized (pool) {
+            // a bit redundant, but this closes a small timing hole...
+            if (destroyed) {
+                try {
+                    mc.destroy();
+                }
+                catch (ResourceException re) { } // ignore
+                return pool.remove(mci.getManagedConnection()) != null;
+            }
+            if (shrinkLater > 0) {
+                //nothing can get in the pool while shrinkLater > 0, so wasInPool is false here.
+                connectionReturnAction = ConnectionReturnAction.DESTROY;
+                shrinkLater--;
+            } else if (connectionReturnAction == ConnectionReturnAction.RETURN_HANDLE) {
+                mci.setLastUsed(System.currentTimeMillis());
+                pool.put(mci.getManagedConnection(), mci);
+                return wasInPool;
+            } else {
+                wasInPool = pool.remove(mci.getManagedConnection()) != null;
+            }
+        }
+        //we must destroy connection.
+        next.returnConnection(connectionInfo, connectionReturnAction);
+        connectionCount--;
+        return wasInPool;
+    }
+
+    protected void internalDestroy() {
+        synchronized (pool) {
+            Iterator it = pool.keySet().iterator();
+            for (; it.hasNext(); ) {
+                try {
+                    ((ManagedConnection)it.next()).destroy();
+                }
+                catch (ResourceException re) { } // ignore
+                it.remove();
+            }
+        }
+    }
+
+    public int getPartitionMaxSize() {
+        return maxSize;
+    }
+
+    public int getIdleConnectionCount() {
+        return pool.size();
+    }
+
+    protected void transferConnections(int maxSize, int shrinkNow) {
+        //1st example: copy 0 (none)
+        //2nd example: copy 10 (all)
+        HashMap oldPool = pool;
+        pool = new HashMap(maxSize);
+        //since we have replaced pool already, pool.remove will be very fast:-)
+        assert oldPool.size() == connectionCount;
+        Iterator it = oldPool.entrySet().iterator();
+        for (int i = 0; i < shrinkNow; i++) {
+            ConnectionInfo killInfo = new ConnectionInfo((ManagedConnectionInfo) ((Map.Entry)it.next()).getValue());
+            internalReturn(killInfo, ConnectionReturnAction.DESTROY);
+        }
+        for (; it.hasNext(); ) {
+            Map.Entry entry = (Map.Entry) it.next();
+            pool.put(entry.getKey(), entry.getValue());
+        }
+
+    }
+
+    protected void getExpiredManagedConnectionInfos(long threshold, ArrayList killList) {
+        synchronized (pool) {
+            for (Iterator iterator = pool.entrySet().iterator(); iterator.hasNext();) {
+                Map.Entry entry = (Map.Entry) iterator.next();
+                ManagedConnectionInfo mci = (ManagedConnectionInfo) entry.getValue();
+                if (mci.getLastUsed() < threshold) {
+                    killList.add(mci);
+                }
+            }
+        }
+
+    }
+
+    protected boolean addToPool(ManagedConnectionInfo mci) {
+        boolean added;
+        synchronized (pool) {
+            connectionCount++;
+            added = getPartitionMaxSize() > getIdleConnectionCount();
+            if (added) {
+                pool.put(mci.getManagedConnection(), mci);
+            }
+        }
+        return added;
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/SubjectInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/SubjectInterceptor.java
new file mode 100644
index 0000000..ea9954c
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/SubjectInterceptor.java
@@ -0,0 +1,102 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.ApplicationServerInternalException;
+import javax.security.auth.Subject;
+
+/**
+ * SubjectInterceptor.java This is installed only when the plan includes a container-managed-security element.
+ *
+ *
+ * Created: Mon Oct  6 14:31:56 2003
+ *
+ * @version $Rev$ $Date$
+ */
+public class SubjectInterceptor implements ConnectionInterceptor {
+
+    private final ConnectionInterceptor next;
+    private final SubjectSource subjectSource;
+
+    public SubjectInterceptor(final ConnectionInterceptor next, final SubjectSource subjectSource) {
+        this.next = next;
+        this.subjectSource = subjectSource;
+    }
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        Subject currentSubject = null;
+        if (!connectionInfo.isApplicationManagedSecurity()) {
+            try {
+                currentSubject = subjectSource.getSubject();
+            } catch (SecurityException e) {
+                throw new ResourceException("Can not obtain Subject for login", e);
+            }
+            if (currentSubject == null) {
+                throw new ResourceException("No subject for container managed security");
+            }
+        }
+        ManagedConnectionInfo originalManagedConnectionInfo = connectionInfo.getManagedConnectionInfo();
+        //No existing managed connection, get an appropriate one and return.
+        if (originalManagedConnectionInfo.getManagedConnection() == null) {
+            originalManagedConnectionInfo.setSubject(currentSubject);
+            next.getConnection(connectionInfo);
+        } else {
+            Subject oldSubject = originalManagedConnectionInfo.getSubject();
+            if (currentSubject == null ? oldSubject != null : !currentSubject.equals(oldSubject)) {
+                if (connectionInfo.isUnshareable()) {
+                    throw new ApplicationServerInternalException("Unshareable resource is attempting to change security context: expected request under: " + oldSubject + ", received request under: " + currentSubject);
+                } else {
+                    //existing managed connection, wrong subject: must re-associate.
+                    //make a ConnectionInfo to process removing the handle from the old mc
+                    ConnectionInfo returningConnectionInfo = new ConnectionInfo();
+                    returningConnectionInfo.setManagedConnectionInfo(originalManagedConnectionInfo);
+                    //This should decrement handle count, but not close the handle, when returnConnection is called
+                    //I'm not sure how to test/assure this.
+                    returningConnectionInfo.setConnectionHandle(connectionInfo.getConnectionHandle());
+
+                    //make a new ManagedConnectionInfo for the mc we will ask for
+                    ManagedConnectionInfo newManagedConnectionInfo =
+                            new ManagedConnectionInfo(
+                                    originalManagedConnectionInfo.getManagedConnectionFactory(),
+                                    originalManagedConnectionInfo.getConnectionRequestInfo());
+                    newManagedConnectionInfo.setSubject(currentSubject);
+                    connectionInfo.setManagedConnectionInfo(newManagedConnectionInfo);
+                    next.getConnection(connectionInfo);
+                    //process the removal of the handle from the previous mc
+                    returnConnection(returningConnectionInfo, ConnectionReturnAction.RETURN_HANDLE);
+                }
+            } else {
+                //otherwise, the current ManagedConnection matches the security info, we keep it.
+                //set up the tx context
+                next.getConnection(connectionInfo);
+            }
+        }
+    }
+
+    public void returnConnection(
+            ConnectionInfo connectionInfo,
+            ConnectionReturnAction connectionReturnAction) {
+        next.returnConnection(connectionInfo, connectionReturnAction);
+    }
+
+    public void destroy() {
+        next.destroy();
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/SubjectSource.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/SubjectSource.java
new file mode 100644
index 0000000..ea83d15
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/SubjectSource.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.security.auth.Subject;
+
+/**
+ * @version $Rev:$ $Date:$
+ */
+public interface SubjectSource {
+    Subject getSubject() throws SecurityException;
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/TCCLInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/TCCLInterceptor.java
new file mode 100644
index 0000000..c32f026
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/TCCLInterceptor.java
@@ -0,0 +1,59 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class TCCLInterceptor implements ConnectionInterceptor{
+    private final ConnectionInterceptor next;
+    private final ClassLoader classLoader;
+
+    public TCCLInterceptor(ConnectionInterceptor next, ClassLoader classLoader) {
+        this.next = next;
+        this.classLoader = classLoader;
+    }
+
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        Thread currentThread = Thread.currentThread();
+        ClassLoader oldClassLoader = currentThread.getContextClassLoader();
+        try {
+            currentThread.setContextClassLoader(classLoader);
+            next.getConnection(connectionInfo);
+        } finally {
+            currentThread.setContextClassLoader(oldClassLoader);
+        }
+    }
+
+    public void returnConnection(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
+        Thread currentThread = Thread.currentThread();
+        ClassLoader oldClassLoader = currentThread.getContextClassLoader();
+        try {
+            currentThread.setContextClassLoader(classLoader);
+            next.returnConnection(connectionInfo, connectionReturnAction);
+        } finally {
+            currentThread.setContextClassLoader(oldClassLoader);
+        }
+    }
+    
+    public void destroy() {
+        this.next.destroy();
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ThreadLocalCachingConnectionInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ThreadLocalCachingConnectionInterceptor.java
new file mode 100644
index 0000000..03ca1c1
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/ThreadLocalCachingConnectionInterceptor.java
@@ -0,0 +1,83 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import java.util.Collections;
+
+import javax.resource.ResourceException;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class ThreadLocalCachingConnectionInterceptor implements ConnectionInterceptor {
+
+    private final ConnectionInterceptor next;
+
+    private final ThreadLocal connections = new ThreadLocal();
+    private final boolean matchConnections;
+
+    public ThreadLocalCachingConnectionInterceptor(final ConnectionInterceptor next, final boolean matchConnections) {
+        this.next = next;
+        this.matchConnections = matchConnections;
+    }
+
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        if (connectionInfo.isUnshareable()) {
+            next.getConnection(connectionInfo);
+            return;
+        }
+        ManagedConnectionInfo managedConnectionInfo = (ManagedConnectionInfo) connections.get();
+        if (managedConnectionInfo != null) {
+            if (matchConnections) {
+                ManagedConnectionInfo mciRequest = connectionInfo.getManagedConnectionInfo();
+                if (null != managedConnectionInfo.getManagedConnectionFactory().matchManagedConnections(
+                        Collections.singleton(managedConnectionInfo.getManagedConnection()),
+                        mciRequest.getSubject(),
+                        mciRequest.getConnectionRequestInfo()
+                )) {
+                    connectionInfo.setManagedConnectionInfo(managedConnectionInfo);
+                    return;
+                } else {
+                    //match failed, get a new cx after returning this one
+                    connections.set(null);
+                    next.returnConnection(connectionInfo, ConnectionReturnAction.RETURN_HANDLE);
+                }
+            } else {
+                connectionInfo.setManagedConnectionInfo(managedConnectionInfo);
+                return;
+            }
+        }
+        //nothing for this thread or match failed
+        next.getConnection(connectionInfo);
+        connections.set(connectionInfo.getManagedConnectionInfo());
+    }
+
+    public void returnConnection(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
+        if (connectionReturnAction == ConnectionReturnAction.DESTROY || connectionInfo.isUnshareable()) {
+            next.returnConnection(connectionInfo, connectionReturnAction);
+        }
+    }
+    
+    public void destroy() {
+        next.destroy();
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/TransactionCachingInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/TransactionCachingInterceptor.java
new file mode 100644
index 0000000..e4ad23b
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/TransactionCachingInterceptor.java
@@ -0,0 +1,214 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.resource.ResourceException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.geronimo.connector.ConnectionReleaser;
+import org.apache.geronimo.connector.ConnectorTransactionContext;
+
+/**
+ * TransactionCachingInterceptor.java
+ * TODO: This implementation does not take account of unshareable resources
+ * TODO: This implementation does not take account of application security
+ * where several connections with different security info are obtained.
+ * TODO: This implementation does not take account of container managed security where,
+ * within one transaction, a security domain boundary is crossed
+ * and connections are obtained with two (or more) different subjects.
+ * <p/>
+ * I suggest a state pattern, with the state set in a threadlocal upon entering a component,
+ * will be a usable implementation.
+ * <p/>
+ * The afterCompletion method will need to move to an interface,  and that interface include the
+ * security info to distinguish connections.
+ * <p/>
+ * <p/>
+ * Created: Mon Sep 29 15:07:07 2003
+ *
+ * @version 1.0
+ */
+public class TransactionCachingInterceptor implements ConnectionInterceptor, ConnectionReleaser {
+    protected static Log log = LogFactory.getLog(TransactionCachingInterceptor.class.getName());
+
+    private final ConnectionInterceptor next;
+    private final TransactionManager transactionManager;
+
+    public TransactionCachingInterceptor(ConnectionInterceptor next, TransactionManager transactionManager) {
+        this.next = next;
+        this.transactionManager = transactionManager;
+    }
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        //There can be an inactive transaction context when a connection is requested in
+        //Synchronization.afterCompletion().
+
+        // get the current transation and status... if there is a problem just assume there is no transaction present
+        Transaction transaction = TxUtil.getTransactionIfActive(transactionManager);
+        if (transaction != null) {
+            ManagedConnectionInfos managedConnectionInfos = ConnectorTransactionContext.get(transaction, this);
+            if (connectionInfo.isUnshareable()) {
+                if (!managedConnectionInfos.containsUnshared(connectionInfo.getManagedConnectionInfo())) {
+                    next.getConnection(connectionInfo);
+                    managedConnectionInfos.addUnshared(connectionInfo.getManagedConnectionInfo());
+                }
+            } else {
+                ManagedConnectionInfo managedConnectionInfo = managedConnectionInfos.getShared();
+                if (managedConnectionInfo != null) {
+                    connectionInfo.setManagedConnectionInfo(managedConnectionInfo);
+                    //return;
+                    if (log.isTraceEnabled()) {
+                        log.trace("supplying connection from tx cache " + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
+                    }
+                } else {
+                    next.getConnection(connectionInfo);
+                    managedConnectionInfos.setShared(connectionInfo.getManagedConnectionInfo());
+                    if (log.isTraceEnabled()) {
+                        log.trace("supplying connection from pool " + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
+                    }
+                }
+            }
+        } else {
+            next.getConnection(connectionInfo);
+        }
+    }
+
+    public void returnConnection(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
+
+        if (connectionReturnAction == ConnectionReturnAction.DESTROY) {
+            if (log.isTraceEnabled()) {
+                log.trace("destroying connection" + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
+            }
+            next.returnConnection(connectionInfo, connectionReturnAction);
+            return;
+        }
+        Transaction transaction;
+        try {
+            transaction = transactionManager.getTransaction();
+            if (transaction != null) {
+                if (TxUtil.isActive(transaction)) {
+                    if (log.isTraceEnabled()) {
+                        log.trace("tx active, not returning connection" + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
+                    }
+                    return;
+                }
+                //We are called from an afterCompletion synchronization.  Remove the MCI from the ManagedConnectionInfos
+                //so we don't close it twice
+                ManagedConnectionInfos managedConnectionInfos = ConnectorTransactionContext.get(transaction, this);
+                managedConnectionInfos.remove(connectionInfo.getManagedConnectionInfo());
+                if (log.isTraceEnabled()) {
+                    log.trace("tx ended, but not removed");
+                }
+            }
+        } catch (SystemException e) {
+            //ignore
+        }
+        if (log.isTraceEnabled()) {
+            log.trace("tx ended, returning connection" + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
+        }
+        internalReturn(connectionInfo, connectionReturnAction);
+    }
+
+    private void internalReturn(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
+        if (connectionInfo.getManagedConnectionInfo().hasConnectionHandles()) {
+            if (log.isTraceEnabled()) {
+                log.trace("not returning connection from tx cache (has handles) " + connectionInfo.getConnectionHandle() + " for managed connection " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
+            }
+            return;
+        }
+        //No transaction, no handles, we return it.
+        next.returnConnection(connectionInfo, connectionReturnAction);
+        if (log.isTraceEnabled()) {
+            log.trace("completed return of connection through tx cache " + connectionInfo.getConnectionHandle() + " for MCI: " + connectionInfo.getManagedConnectionInfo() + " and MC " + connectionInfo.getManagedConnectionInfo().getManagedConnection() + " to tx caching interceptor " + this);
+        }
+    }
+
+    public void destroy() {
+        next.destroy();
+    }
+
+    public void afterCompletion(Object stuff) {
+        ManagedConnectionInfos managedConnectionInfos = (ManagedConnectionInfos) stuff;
+        ManagedConnectionInfo sharedMCI = managedConnectionInfos.getShared();
+        if (sharedMCI != null) {
+            if (log.isTraceEnabled()) {
+                log.trace("Transaction completed, attempting to return shared connection MCI: " + sharedMCI + " for managed connection " + sharedMCI.getManagedConnection() + " to tx caching interceptor " + this);
+            }
+            returnHandle(sharedMCI);
+        }
+        for (Iterator iterator = managedConnectionInfos.getUnshared().iterator(); iterator.hasNext();) {
+            ManagedConnectionInfo managedConnectionInfo = (ManagedConnectionInfo) iterator.next();
+            if (log.isTraceEnabled()) {
+                log.trace("Transaction completed, attempting to return unshared connection MCI: " + managedConnectionInfo + " for managed connection " + managedConnectionInfo.getManagedConnection() + " to tx caching interceptor " + this);
+            }
+            returnHandle(managedConnectionInfo);
+        }
+    }
+
+    private void returnHandle(ManagedConnectionInfo managedConnectionInfo) {
+        ConnectionInfo connectionInfo = new ConnectionInfo();
+        connectionInfo.setManagedConnectionInfo(managedConnectionInfo);
+        internalReturn(connectionInfo, ConnectionReturnAction.RETURN_HANDLE);
+    }
+
+    public static class ManagedConnectionInfos {
+        private ManagedConnectionInfo shared;
+        private Set unshared = Collections.EMPTY_SET;
+
+        public ManagedConnectionInfo getShared() {
+            return shared;
+        }
+
+        public void setShared(ManagedConnectionInfo shared) {
+            this.shared = shared;
+        }
+
+        public Set getUnshared() {
+            return unshared;
+        }
+
+        public void addUnshared(ManagedConnectionInfo unsharedMCI) {
+            if (this.unshared == Collections.EMPTY_SET) {
+                this.unshared = new HashSet();
+            }
+            this.unshared.add(unsharedMCI);
+        }
+
+        public boolean containsUnshared(ManagedConnectionInfo unsharedMCI) {
+            return this.unshared.contains(unsharedMCI);
+        }
+
+        public void remove(ManagedConnectionInfo managedConnectionInfo) {
+            if (shared == managedConnectionInfo) {
+                shared = null;
+            } else {
+                unshared.remove(managedConnectionInfo);
+            }
+        }
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/TransactionEnlistingInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/TransactionEnlistingInterceptor.java
new file mode 100644
index 0000000..c0e678e
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/TransactionEnlistingInterceptor.java
@@ -0,0 +1,102 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+
+/**
+ * TransactionEnlistingInterceptor.java
+ * <p/>
+ * <p/>
+ * Created: Fri Sep 26 14:52:24 2003
+ *
+ * @version 1.0
+ */
+public class TransactionEnlistingInterceptor implements ConnectionInterceptor {
+
+    private final ConnectionInterceptor next;
+    private final TransactionManager transactionManager;
+
+    public TransactionEnlistingInterceptor(ConnectionInterceptor next, TransactionManager transactionManager) {
+        this.next = next;
+        this.transactionManager = transactionManager;
+    }
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        next.getConnection(connectionInfo);
+        try {
+            ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+
+            // get the current transation and status... if there is a problem just assume there is no transaction present
+            Transaction transaction = TxUtil.getTransactionIfActive(transactionManager);
+            if (transaction != null) {
+                XAResource xares = mci.getXAResource();
+                transaction.enlistResource(xares);
+            }
+        } catch (SystemException e) {
+            returnConnection(connectionInfo, ConnectionReturnAction.DESTROY);
+            throw new ResourceException("Could not get transaction", e);
+        } catch (RollbackException e) {
+            //transaction is marked rolled back, so the xaresource could not have been enlisted
+            next.returnConnection(connectionInfo, ConnectionReturnAction.RETURN_HANDLE);
+            throw new ResourceException("Could not enlist resource in rolled back transaction", e);
+        } catch (Throwable t) {
+            returnConnection(connectionInfo, ConnectionReturnAction.DESTROY);
+            throw new ResourceException("Unknown throwable when trying to enlist connection in tx", t);
+        }
+    }
+
+    /**
+     * The <code>returnConnection</code> method
+     * <p/>
+     * todo Probably the logic needs improvement if a connection
+     * error occurred and we are destroying the handle.
+     *
+     * @param connectionInfo         a <code>ConnectionInfo</code> value
+     * @param connectionReturnAction a <code>ConnectionReturnAction</code> value
+     */
+    public void returnConnection(ConnectionInfo connectionInfo,
+                                 ConnectionReturnAction connectionReturnAction) {
+        try {
+            ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+            Transaction transaction = TxUtil.getTransactionIfActive(transactionManager);
+            if (transaction != null) {
+                XAResource xares = mci.getXAResource();
+                transaction.delistResource(xares, XAResource.TMSUSPEND);
+            }
+
+        } catch (SystemException e) {
+            //maybe we should warn???
+            connectionReturnAction = ConnectionReturnAction.DESTROY;
+        } catch (IllegalStateException e) {
+            connectionReturnAction = ConnectionReturnAction.DESTROY;
+        }
+
+        next.returnConnection(connectionInfo, connectionReturnAction);
+    }
+
+    public void destroy() {
+        next.destroy();
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/TxUtil.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/TxUtil.java
new file mode 100644
index 0000000..ea6e602
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/TxUtil.java
@@ -0,0 +1,64 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector.outbound;
+
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public final class TxUtil {
+    private TxUtil() {
+    }
+
+    public static Transaction getTransactionIfActive(TransactionManager transactionManager) {
+        Transaction transaction = null;
+        int status = Status.STATUS_NO_TRANSACTION;
+        try {
+            transaction = transactionManager.getTransaction();
+            if (transaction != null) status = transaction.getStatus();
+        } catch (SystemException ignored) {
+        }
+
+        if (transaction != null && status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK) {
+            return transaction;
+        }
+        return null;
+    }
+
+    public static boolean isTransactionActive(TransactionManager transactionManager) {
+        try {
+            int status = transactionManager.getStatus();
+            return status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK;
+        } catch (SystemException ignored) {
+            return false;
+        }
+    }
+
+    public static boolean isActive(Transaction transaction) {
+        try {
+            int status = transaction.getStatus();
+            return status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK;
+        } catch (SystemException ignored) {
+            return false;
+        }
+    }
+}
+
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/XAResourceInsertionInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/XAResourceInsertionInterceptor.java
new file mode 100644
index 0000000..f2f8d41
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/XAResourceInsertionInterceptor.java
@@ -0,0 +1,54 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+
+import org.apache.geronimo.transaction.manager.WrapperNamedXAResource;
+
+/**
+ * XAResourceInsertionInterceptor.java
+ *
+ *
+ * @version $Rev$ $Date$
+ */
+public class XAResourceInsertionInterceptor implements ConnectionInterceptor {
+
+    private final ConnectionInterceptor next;
+    private final String name;
+
+    public XAResourceInsertionInterceptor(final ConnectionInterceptor next, final String name) {
+        this.next = next;
+        this.name = name;
+    }
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        next.getConnection(connectionInfo);
+        ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+        mci.setXAResource(new WrapperNamedXAResource(mci.getManagedConnection().getXAResource(), name));
+    }
+
+    public void returnConnection(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
+        next.returnConnection(connectionInfo, connectionReturnAction);
+    }
+    
+    public void destroy() {
+        next.destroy();
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/LocalTransactions.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/LocalTransactions.java
new file mode 100644
index 0000000..4d3022c
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/LocalTransactions.java
@@ -0,0 +1,51 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectionmanagerconfig;
+
+import javax.transaction.TransactionManager;
+
+import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
+import org.apache.geronimo.connector.outbound.LocalXAResourceInsertionInterceptor;
+import org.apache.geronimo.connector.outbound.TransactionCachingInterceptor;
+import org.apache.geronimo.connector.outbound.TransactionEnlistingInterceptor;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class LocalTransactions extends TransactionSupport {
+    public static final TransactionSupport INSTANCE = new LocalTransactions();
+
+    private LocalTransactions() {
+    }
+
+    public ConnectionInterceptor addXAResourceInsertionInterceptor(ConnectionInterceptor stack, String name) {
+        return new LocalXAResourceInsertionInterceptor(stack, name);
+    }
+
+    public ConnectionInterceptor addTransactionInterceptors(ConnectionInterceptor stack, TransactionManager transactionManager) {
+        stack = new TransactionEnlistingInterceptor(stack, transactionManager);
+        return new TransactionCachingInterceptor(stack, transactionManager);
+    }
+    
+    public boolean isRecoverable() {
+        return false;
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/NoPool.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/NoPool.java
new file mode 100644
index 0000000..9364f17
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/NoPool.java
@@ -0,0 +1,76 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectionmanagerconfig;
+
+import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class NoPool implements PoolingSupport {
+    public ConnectionInterceptor addPoolingInterceptors(ConnectionInterceptor tail) {
+        return tail;
+    }
+
+    public int getPartitionCount() {
+        return 0;
+    }
+
+    public int getIdleConnectionCount() {
+        return 0;
+    }
+
+    public int getConnectionCount() {
+        return 0;
+    }
+
+    public int getPartitionMaxSize() {
+        return 0;
+    }
+
+    public void setPartitionMaxSize(int maxSize) {
+
+    }
+
+    public int getPartitionMinSize() {
+        return 0;
+    }
+
+    public void setPartitionMinSize(int minSize) {
+
+    }
+
+    public int getBlockingTimeoutMilliseconds() {
+        return 0;
+    }
+
+    public void setBlockingTimeoutMilliseconds(int timeoutMilliseconds) {
+
+    }
+
+    public int getIdleTimeoutMinutes() {
+        return 0;
+    }
+
+    public void setIdleTimeoutMinutes(int idleTimeoutMinutes) {
+
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/NoTransactions.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/NoTransactions.java
new file mode 100644
index 0000000..536aea1
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/NoTransactions.java
@@ -0,0 +1,47 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectionmanagerconfig;
+
+import javax.transaction.TransactionManager;
+
+import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class NoTransactions extends TransactionSupport {
+    public static final TransactionSupport INSTANCE = new NoTransactions();
+
+    private NoTransactions() {
+    }
+
+    public ConnectionInterceptor addXAResourceInsertionInterceptor(ConnectionInterceptor stack, String name) {
+        return stack;
+    }
+
+    public ConnectionInterceptor addTransactionInterceptors(ConnectionInterceptor stack, TransactionManager transactionManager) {
+        return stack;
+    }
+    
+    public boolean isRecoverable() {
+        return false;
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/PartitionedPool.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/PartitionedPool.java
new file mode 100644
index 0000000..53d3ab5
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/PartitionedPool.java
@@ -0,0 +1,142 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectionmanagerconfig;
+
+import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
+import org.apache.geronimo.connector.outbound.MultiPoolConnectionInterceptor;
+import org.apache.geronimo.connector.outbound.PoolingAttributes;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class PartitionedPool implements PoolingSupport {
+
+    private boolean partitionByConnectionRequestInfo;
+    private boolean partitionBySubject;
+
+    private final SinglePool singlePool;
+
+    private transient PoolingAttributes poolingAttributes;
+
+    public PartitionedPool(int maxSize, int minSize, int blockingTimeoutMilliseconds, int idleTimeoutMinutes, boolean matchOne, boolean matchAll, boolean selectOneAssumeMatch, boolean partitionByConnectionRequestInfo, boolean partitionBySubject) {
+        singlePool = new SinglePool(maxSize, minSize, blockingTimeoutMilliseconds, idleTimeoutMinutes, matchOne, matchAll, selectOneAssumeMatch);
+        this.partitionByConnectionRequestInfo = partitionByConnectionRequestInfo;
+        this.partitionBySubject = partitionBySubject;
+    }
+
+    public boolean isPartitionByConnectionRequestInfo() {
+        return partitionByConnectionRequestInfo;
+    }
+
+    public void setPartitionByConnectionRequestInfo(boolean partitionByConnectionRequestInfo) {
+        this.partitionByConnectionRequestInfo = partitionByConnectionRequestInfo;
+    }
+
+    public boolean isPartitionBySubject() {
+        return partitionBySubject;
+    }
+
+    public void setPartitionBySubject(boolean partitionBySubject) {
+        this.partitionBySubject = partitionBySubject;
+    }
+
+    public int getMaxSize() {
+        return singlePool.getMaxSize();
+    }
+
+    public void setMaxSize(int maxSize) {
+        singlePool.setMaxSize(maxSize);
+    }
+
+    public int getBlockingTimeoutMilliseconds() {
+        return poolingAttributes.getBlockingTimeoutMilliseconds();
+    }
+
+    public void setBlockingTimeoutMilliseconds(int blockingTimeoutMilliseconds) {
+        poolingAttributes.setBlockingTimeoutMilliseconds(blockingTimeoutMilliseconds);
+    }
+
+    public int getIdleTimeoutMinutes() {
+        return poolingAttributes.getIdleTimeoutMinutes();
+    }
+
+    public void setIdleTimeoutMinutes(int idleTimeoutMinutes) {
+        poolingAttributes.setIdleTimeoutMinutes(idleTimeoutMinutes);
+    }
+
+    public boolean isMatchOne() {
+        return singlePool.isMatchOne();
+    }
+
+    public void setMatchOne(boolean matchOne) {
+        singlePool.setMatchOne(matchOne);
+    }
+
+    public boolean isMatchAll() {
+        return singlePool.isMatchAll();
+    }
+
+    public void setMatchAll(boolean matchAll) {
+        singlePool.setMatchAll(matchAll);
+    }
+
+    public boolean isSelectOneAssumeMatch() {
+        return singlePool.isSelectOneAssumeMatch();
+    }
+
+    public void setSelectOneAssumeMatch(boolean selectOneAssumeMatch) {
+        singlePool.setSelectOneAssumeMatch(selectOneAssumeMatch);
+    }
+
+    public ConnectionInterceptor addPoolingInterceptors(ConnectionInterceptor tail) {
+        MultiPoolConnectionInterceptor pool = new MultiPoolConnectionInterceptor(tail,
+                singlePool,
+                isPartitionBySubject(),
+                isPartitionByConnectionRequestInfo());
+        this.poolingAttributes = pool;
+        return pool;
+    }
+
+    public int getPartitionCount() {
+        return poolingAttributes.getPartitionCount();
+    }
+
+    public int getPartitionMaxSize() {
+        return poolingAttributes.getPartitionMaxSize();
+    }
+
+    public void setPartitionMaxSize(int maxSize) throws InterruptedException {
+        poolingAttributes.setPartitionMaxSize(maxSize);
+    }
+
+    public int getPartitionMinSize() {
+        return poolingAttributes.getPartitionMinSize();
+    }
+
+    public void setPartitionMinSize(int minSize) {
+        poolingAttributes.setPartitionMinSize(minSize);
+    }
+
+    public int getIdleConnectionCount() {
+        return poolingAttributes.getIdleConnectionCount();
+    }
+
+    public int getConnectionCount() {
+        return poolingAttributes.getConnectionCount();
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/PoolingSupport.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/PoolingSupport.java
new file mode 100644
index 0000000..95f6eb9
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/PoolingSupport.java
@@ -0,0 +1,32 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectionmanagerconfig;
+
+import java.io.Serializable;
+
+import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
+import org.apache.geronimo.connector.outbound.PoolingAttributes;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public interface PoolingSupport extends Serializable, PoolingAttributes {
+
+    ConnectionInterceptor addPoolingInterceptors(ConnectionInterceptor tail);
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/SinglePool.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/SinglePool.java
new file mode 100644
index 0000000..96c8099
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/SinglePool.java
@@ -0,0 +1,166 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectionmanagerconfig;
+
+import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
+import org.apache.geronimo.connector.outbound.PoolingAttributes;
+import org.apache.geronimo.connector.outbound.SinglePoolConnectionInterceptor;
+import org.apache.geronimo.connector.outbound.SinglePoolMatchAllConnectionInterceptor;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class SinglePool implements PoolingSupport {
+    private int maxSize;
+    private int minSize;
+    private int blockingTimeoutMilliseconds;
+    private int idleTimeoutMinutes;
+    private boolean matchOne;
+    private boolean matchAll;
+    private boolean selectOneAssumeMatch;
+
+    private transient PoolingAttributes pool;
+
+    public SinglePool(int maxSize, int minSize, int blockingTimeoutMilliseconds, int idleTimeoutMinutes, boolean matchOne, boolean matchAll, boolean selectOneAssumeMatch) {
+        this.maxSize = maxSize;
+        this.minSize = minSize;
+        this.blockingTimeoutMilliseconds = blockingTimeoutMilliseconds;
+        this.idleTimeoutMinutes = idleTimeoutMinutes;
+        this.matchOne = matchOne;
+        this.matchAll = matchAll;
+        this.selectOneAssumeMatch = selectOneAssumeMatch;
+    }
+
+    public int getMaxSize() {
+        return maxSize;
+    }
+
+    public void setMaxSize(int maxSize) {
+        this.maxSize = maxSize;
+    }
+
+    public int getMinSize() {
+        return minSize;
+    }
+
+    public void setMinSize(int minSize) {
+        this.minSize = minSize;
+    }
+
+    public int getBlockingTimeoutMilliseconds() {
+        return blockingTimeoutMilliseconds;
+    }
+
+    public void setBlockingTimeoutMilliseconds(int blockingTimeoutMilliseconds) {
+        this.blockingTimeoutMilliseconds = blockingTimeoutMilliseconds;
+        if (pool != null) {
+            pool.setBlockingTimeoutMilliseconds(blockingTimeoutMilliseconds);
+        }
+    }
+
+    public int getIdleTimeoutMinutes() {
+        return idleTimeoutMinutes;
+    }
+
+    public void setIdleTimeoutMinutes(int idleTimeoutMinutes) {
+        this.idleTimeoutMinutes = idleTimeoutMinutes;
+        if (pool != null) {
+            pool.setIdleTimeoutMinutes(idleTimeoutMinutes);
+        }
+    }
+
+    public boolean isMatchOne() {
+        return matchOne;
+    }
+
+    public void setMatchOne(boolean matchOne) {
+        this.matchOne = matchOne;
+    }
+
+    public boolean isMatchAll() {
+        return matchAll;
+    }
+
+    public void setMatchAll(boolean matchAll) {
+        this.matchAll = matchAll;
+    }
+
+    public boolean isSelectOneAssumeMatch() {
+        return selectOneAssumeMatch;
+    }
+
+    public void setSelectOneAssumeMatch(boolean selectOneAssumeMatch) {
+        this.selectOneAssumeMatch = selectOneAssumeMatch;
+    }
+
+    public ConnectionInterceptor addPoolingInterceptors(ConnectionInterceptor tail) {
+        if (isMatchAll()) {
+            SinglePoolMatchAllConnectionInterceptor pool = new SinglePoolMatchAllConnectionInterceptor(tail,
+                    getMaxSize(),
+                    getMinSize(),
+                    getBlockingTimeoutMilliseconds(),
+                    getIdleTimeoutMinutes());
+            this.pool = pool;
+            return pool;
+
+        } else {
+            SinglePoolConnectionInterceptor pool = new SinglePoolConnectionInterceptor(tail,
+                    getMaxSize(),
+                    getMinSize(),
+                    getBlockingTimeoutMilliseconds(),
+                    getIdleTimeoutMinutes(),
+                    isSelectOneAssumeMatch());
+            this.pool = pool;
+            return pool;
+        }
+    }
+
+    public int getPartitionCount() {
+        return 1;
+    }
+
+    public int getPartitionMaxSize() {
+        return maxSize;
+    }
+
+    public void setPartitionMaxSize(int maxSize) throws InterruptedException {
+        if (pool != null) {
+            pool.setPartitionMaxSize(maxSize);
+        }
+        this.maxSize = maxSize;
+    }
+
+    public int getPartitionMinSize() {
+        return minSize;
+    }
+
+    public void setPartitionMinSize(int minSize) {
+        if (pool != null) {
+            pool.setPartitionMinSize(minSize);
+        }
+        this.minSize = minSize;
+    }
+
+    public int getIdleConnectionCount() {
+        return pool == null ? 0 : pool.getIdleConnectionCount();
+    }
+
+    public int getConnectionCount() {
+        return pool == null ? 0 : pool.getConnectionCount();
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/TransactionLog.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/TransactionLog.java
new file mode 100644
index 0000000..b11d24a
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/TransactionLog.java
@@ -0,0 +1,52 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectionmanagerconfig;
+
+import javax.transaction.TransactionManager;
+
+import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
+import org.apache.geronimo.connector.outbound.TransactionCachingInterceptor;
+import org.apache.geronimo.connector.outbound.TransactionEnlistingInterceptor;
+import org.apache.geronimo.connector.outbound.transactionlog.LogXAResourceInsertionInterceptor;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class TransactionLog extends TransactionSupport
+{
+    public static final TransactionSupport INSTANCE = new TransactionLog();
+
+    private TransactionLog() {
+    }
+
+    public ConnectionInterceptor addXAResourceInsertionInterceptor(ConnectionInterceptor stack, String name) {
+        return new LogXAResourceInsertionInterceptor(stack, name);
+    }
+
+    public ConnectionInterceptor addTransactionInterceptors(ConnectionInterceptor stack, TransactionManager transactionManager) {
+        stack = new TransactionEnlistingInterceptor(stack, transactionManager);
+        return new TransactionCachingInterceptor(stack, transactionManager);
+    }
+    
+    public boolean isRecoverable() {
+        return false;
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/TransactionSupport.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/TransactionSupport.java
new file mode 100644
index 0000000..f960b3f
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/TransactionSupport.java
@@ -0,0 +1,36 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectionmanagerconfig;
+
+import java.io.Serializable;
+
+import javax.transaction.TransactionManager;
+
+import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public abstract class TransactionSupport implements Serializable {
+    public abstract ConnectionInterceptor addXAResourceInsertionInterceptor(ConnectionInterceptor stack, String name);
+    public abstract ConnectionInterceptor addTransactionInterceptors(ConnectionInterceptor stack, TransactionManager transactionManager);
+    public abstract boolean isRecoverable();
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/XATransactions.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/XATransactions.java
new file mode 100644
index 0000000..e1ff6c9
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectionmanagerconfig/XATransactions.java
@@ -0,0 +1,80 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectionmanagerconfig;
+
+import javax.transaction.TransactionManager;
+
+import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
+import org.apache.geronimo.connector.outbound.ThreadLocalCachingConnectionInterceptor;
+import org.apache.geronimo.connector.outbound.TransactionCachingInterceptor;
+import org.apache.geronimo.connector.outbound.TransactionEnlistingInterceptor;
+import org.apache.geronimo.connector.outbound.XAResourceInsertionInterceptor;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class XATransactions extends TransactionSupport {
+    
+    private boolean useTransactionCaching;
+    private boolean useThreadCaching;
+
+    public XATransactions(boolean useTransactionCaching, boolean useThreadCaching) {
+        this.useTransactionCaching = useTransactionCaching;
+        this.useThreadCaching = useThreadCaching;
+    }
+
+    public boolean isUseTransactionCaching() {
+        return useTransactionCaching;
+    }
+
+    public void setUseTransactionCaching(boolean useTransactionCaching) {
+        this.useTransactionCaching = useTransactionCaching;
+    }
+
+    public boolean isUseThreadCaching() {
+        return useThreadCaching;
+    }
+
+    public void setUseThreadCaching(boolean useThreadCaching) {
+        this.useThreadCaching = useThreadCaching;
+    }
+
+    public ConnectionInterceptor addXAResourceInsertionInterceptor(ConnectionInterceptor stack, String name) {
+        return new XAResourceInsertionInterceptor(stack, name);
+    }
+
+    public ConnectionInterceptor addTransactionInterceptors(ConnectionInterceptor stack, TransactionManager transactionManager) {
+        //experimental thread local caching
+        if (isUseThreadCaching()) {
+            //useMatching should be configurable
+            stack = new ThreadLocalCachingConnectionInterceptor(stack, false);
+        }
+        stack = new TransactionEnlistingInterceptor(stack, transactionManager);
+        if (isUseTransactionCaching()) {
+            stack = new TransactionCachingInterceptor(stack, transactionManager);
+        }
+        return stack;
+    }
+    
+    public boolean isRecoverable() {
+        return true;
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectionTracker.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectionTracker.java
new file mode 100644
index 0000000..26a94b4
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectionTracker.java
@@ -0,0 +1,44 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectiontracking;
+
+import javax.resource.ResourceException;
+
+import org.apache.geronimo.connector.outbound.ConnectionInfo;
+import org.apache.geronimo.connector.outbound.ConnectionReturnAction;
+import org.apache.geronimo.connector.outbound.ConnectionTrackingInterceptor;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public interface ConnectionTracker {
+    void handleObtained(
+            ConnectionTrackingInterceptor connectionTrackingInterceptor,
+            ConnectionInfo connectionInfo,
+            boolean reassociate) throws ResourceException;
+
+    void handleReleased(
+            ConnectionTrackingInterceptor connectionTrackingInterceptor,
+            ConnectionInfo connectionInfo,
+            ConnectionReturnAction connectionReturnAction);
+
+    void setEnvironment(ConnectionInfo connectionInfo, String key);
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectionTrackingCoordinator.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectionTrackingCoordinator.java
new file mode 100644
index 0000000..4cf0b0c
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectionTrackingCoordinator.java
@@ -0,0 +1,377 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectiontracking;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.DissociatableManagedConnection;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.geronimo.connector.outbound.ConnectionInfo;
+import org.apache.geronimo.connector.outbound.ConnectionReturnAction;
+import org.apache.geronimo.connector.outbound.ConnectionTrackingInterceptor;
+import org.apache.geronimo.connector.outbound.ManagedConnectionInfo;
+
+/**
+ * ConnectionTrackingCoordinator tracks connections that are in use by
+ * components such as EJB's.  The component must notify the ccm
+ * when a method enters and exits.  On entrance, the ccm will
+ * notify ConnectionManager stacks so the stack can make sure all
+ * connection handles left open from previous method calls are
+ * attached to ManagedConnections of the correct security context, and
+ * the ManagedConnections are enrolled in any current transaction.
+ * On exit, the ccm will notify ConnectionManager stacks of the handles
+ * left open, so they may be disassociated if appropriate.
+ * In addition, when a UserTransaction is started the ccm will notify
+ * ConnectionManager stacks so the existing ManagedConnections can be
+ * enrolled properly.
+ *
+ * @version $Rev$ $Date$
+ */
+public class ConnectionTrackingCoordinator implements TrackedConnectionAssociator, ConnectionTracker {
+    private static final Log log = LogFactory.getLog(ConnectionTrackingCoordinator.class.getName());
+
+    private final boolean lazyConnect;
+    private final ThreadLocal currentInstanceContexts = new ThreadLocal();
+    private final ConcurrentMap proxiesByConnectionInfo = new ConcurrentHashMap();
+
+    public ConnectionTrackingCoordinator() {
+        this(false);
+    }
+
+    public ConnectionTrackingCoordinator(boolean lazyConnect) {
+        this.lazyConnect = lazyConnect;
+    }
+
+    public boolean isLazyConnect() {
+        return lazyConnect;
+    }
+
+    public ConnectorInstanceContext enter(ConnectorInstanceContext newContext) throws ResourceException {
+        ConnectorInstanceContext oldContext = (ConnectorInstanceContext) currentInstanceContexts.get();
+        currentInstanceContexts.set(newContext);
+        associateConnections(newContext);
+        return oldContext;
+    }
+
+    private void associateConnections(ConnectorInstanceContext context) throws ResourceException {
+            Map connectionManagerToManagedConnectionInfoMap = context.getConnectionManagerMap();
+            for (Iterator i = connectionManagerToManagedConnectionInfoMap.entrySet().iterator(); i.hasNext();) {
+                Map.Entry entry = (Map.Entry) i.next();
+                ConnectionTrackingInterceptor mcci =
+                        (ConnectionTrackingInterceptor) entry.getKey();
+                Set connections = (Set) entry.getValue();
+                mcci.enter(connections);
+            }
+    }
+
+    public void newTransaction() throws ResourceException {
+        ConnectorInstanceContext currentContext = (ConnectorInstanceContext) currentInstanceContexts.get();
+        if (currentContext == null) {
+            return;
+        }
+        associateConnections(currentContext);
+    }
+
+    public void exit(ConnectorInstanceContext oldContext) throws ResourceException {
+        ConnectorInstanceContext currentContext = (ConnectorInstanceContext) currentInstanceContexts.get();
+        try {
+            // for each connection type opened in this componet
+            Map resources = currentContext.getConnectionManagerMap();
+            for (Iterator i = resources.entrySet().iterator(); i.hasNext();) {
+                Map.Entry entry = (Map.Entry) i.next();
+                ConnectionTrackingInterceptor mcci =
+                        (ConnectionTrackingInterceptor) entry.getKey();
+                Set connections = (Set) entry.getValue();
+
+                // release proxy connections
+                if (lazyConnect) {
+                    for (Iterator infoIterator = connections.iterator(); infoIterator.hasNext();) {
+                        ConnectionInfo connectionInfo = (ConnectionInfo) infoIterator.next();
+                        releaseProxyConnection(connectionInfo);
+                    }
+                }
+
+                // use connection interceptor to dissociate connections that support disassociation
+                mcci.exit(connections);
+
+                // if no connection remain clear context... we could support automatic commit, rollback or exception here
+                if (connections.isEmpty()) {
+                    i.remove();
+                }
+            }
+        } finally {
+            // when lazy we do not need or want to track open connections... they will automatically reconnect
+            if (lazyConnect) {
+                currentContext.getConnectionManagerMap().clear();
+            }
+            currentInstanceContexts.set(oldContext);
+        }
+    }
+
+    /**
+     * A new connection (handle) has been obtained.  If we are within a component context, store the connection handle
+     * so we can disassociate connections that support disassociation on exit.
+     * @param connectionTrackingInterceptor our interceptor in the connection manager which is used to disassociate the connections
+     * @param connectionInfo the connection that was obtained
+     * @param reassociate
+     */
+    public void handleObtained(ConnectionTrackingInterceptor connectionTrackingInterceptor,
+            ConnectionInfo connectionInfo,
+            boolean reassociate) throws ResourceException {
+
+        ConnectorInstanceContext currentContext = (ConnectorInstanceContext) currentInstanceContexts.get();
+        if (currentContext == null) {
+            return;
+        }
+
+        Map resources = currentContext.getConnectionManagerMap();
+        Set infos = (Set) resources.get(connectionTrackingInterceptor);
+        if (infos == null) {
+            infos = new HashSet();
+            resources.put(connectionTrackingInterceptor, infos);
+        }
+
+        infos.add(connectionInfo);
+
+        // if lazyConnect, we must proxy so we know when to connect the proxy
+        if (!reassociate && lazyConnect) {
+            proxyConnection(connectionTrackingInterceptor, connectionInfo);
+        }
+    }
+
+    /**
+     * A connection (handle) has been released or destroyed.  If we are within a component context, remove the connection
+     * handle from the context.
+     * @param connectionTrackingInterceptor our interceptor in the connection manager
+     * @param connectionInfo the connection that was released
+     * @param connectionReturnAction
+     */
+    public void handleReleased(ConnectionTrackingInterceptor connectionTrackingInterceptor,
+            ConnectionInfo connectionInfo,
+            ConnectionReturnAction connectionReturnAction) {
+
+        ConnectorInstanceContext currentContext = (ConnectorInstanceContext) currentInstanceContexts.get();
+        if (currentContext == null) {
+            return;
+        }
+
+        Map resources = currentContext.getConnectionManagerMap();
+        Set infos = (Set) resources.get(connectionTrackingInterceptor);
+        if (infos != null) {
+            if (connectionInfo.getConnectionHandle() == null) {
+                //destroy was called as a result of an error
+                ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+                Collection toRemove = mci.getConnectionInfos();
+                infos.removeAll(toRemove);
+            } else {
+                infos.remove(connectionInfo);
+            }
+        } else {
+            if ( log.isTraceEnabled()) {
+                 log.trace("No infos found for handle " + connectionInfo.getConnectionHandle() +
+                         " for MCI: " + connectionInfo.getManagedConnectionInfo() +
+                         " for MC: " + connectionInfo.getManagedConnectionInfo().getManagedConnection() +
+                         " for CTI: " + connectionTrackingInterceptor, new Exception("Stack Trace"));
+            }
+        }
+
+        // NOTE: This method is also called by DissociatableManagedConnection when a connection has been
+        // dissociated in addition to the normal connection closed notification, but this is not a problem
+        // because DissociatableManagedConnection are not proied so this method will have no effect
+        closeProxyConnection(connectionInfo);
+    }
+
+    /**
+     * If we are within a component context, before a connection is obtained, set the connection unshareable and
+     * applicationManagedSecurity properties so the correct connection type is obtained.
+     * @param connectionInfo the connection to be obtained
+     * @param key the unique id of the connection manager
+     */
+    public void setEnvironment(ConnectionInfo connectionInfo, String key) {
+        ConnectorInstanceContext currentContext = (ConnectorInstanceContext) currentInstanceContexts.get();
+        if (currentContext != null) {
+            // is this resource unshareable in this component context
+            Set unshareableResources = currentContext.getUnshareableResources();
+            boolean unshareable = unshareableResources.contains(key);
+            connectionInfo.setUnshareable(unshareable);
+
+            // does this resource use application managed security in this component context
+            Set applicationManagedSecurityResources = currentContext.getApplicationManagedSecurityResources();
+            boolean applicationManagedSecurity = applicationManagedSecurityResources.contains(key);
+            connectionInfo.setApplicationManagedSecurity(applicationManagedSecurity);
+        }
+    }
+
+    private void proxyConnection(ConnectionTrackingInterceptor connectionTrackingInterceptor, ConnectionInfo connectionInfo) throws ResourceException {
+        // if this connection already has a proxy no need to create another
+        if (connectionInfo.getConnectionProxy() != null) return;
+
+        // DissociatableManagedConnection do not need to be proxied
+        if (connectionInfo.getManagedConnectionInfo().getManagedConnection() instanceof DissociatableManagedConnection) {
+            return;
+        }
+
+        try {
+            Object handle = connectionInfo.getConnectionHandle();
+            ConnectionInvocationHandler invocationHandler = new ConnectionInvocationHandler(connectionTrackingInterceptor, connectionInfo, handle);
+            Object proxy = Proxy.newProxyInstance(getClassLoader(handle), handle.getClass().getInterfaces(), invocationHandler);
+
+            // add it to our map... if the map already has a proxy for this connection, use the existing one
+            Object existingProxy = proxiesByConnectionInfo.putIfAbsent(connectionInfo, proxy);
+            if (existingProxy != null) proxy = existingProxy;
+
+            connectionInfo.setConnectionProxy(proxy);
+        } catch (Throwable e) {
+            throw new ResourceException("Unable to construct connection proxy", e);
+        }
+    }
+
+    private void releaseProxyConnection(ConnectionInfo connectionInfo) {
+        ConnectionInvocationHandler invocationHandler = getConnectionInvocationHandler(connectionInfo);
+        if (invocationHandler != null) {
+            invocationHandler.releaseHandle();
+        }
+    }
+
+    private void closeProxyConnection(ConnectionInfo connectionInfo) {
+        ConnectionInvocationHandler invocationHandler = getConnectionInvocationHandler(connectionInfo);
+        if (invocationHandler != null) {
+            invocationHandler.close();
+            proxiesByConnectionInfo.remove(connectionInfo);
+            connectionInfo.setConnectionProxy(null);
+        }
+    }
+
+    // Favor the thread context class loader for proxy construction
+    private ClassLoader getClassLoader(Object handle) {
+        ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader();
+        if (threadClassLoader != null) {
+            return threadClassLoader;
+        }
+        return handle.getClass().getClassLoader();
+    }
+
+    private ConnectionInvocationHandler getConnectionInvocationHandler(ConnectionInfo connectionInfo) {
+        Object proxy = connectionInfo.getConnectionProxy();
+        if (proxy == null) {
+            proxy = proxiesByConnectionInfo.get(connectionInfo);
+        }
+
+        // no proxy or proxy already destroyed
+        if (proxy == null) return null;
+
+        if (Proxy.isProxyClass(proxy.getClass())) {
+            InvocationHandler invocationHandler = Proxy.getInvocationHandler(proxy);
+            if (invocationHandler instanceof ConnectionInvocationHandler) {
+                return (ConnectionInvocationHandler) invocationHandler;
+            }
+        }
+        return null;
+    }
+
+    public static class ConnectionInvocationHandler implements InvocationHandler {
+        private ConnectionTrackingInterceptor connectionTrackingInterceptor;
+        private ConnectionInfo connectionInfo;
+        private final Object handle;
+        private boolean released = false;
+
+        public ConnectionInvocationHandler(ConnectionTrackingInterceptor connectionTrackingInterceptor, ConnectionInfo connectionInfo, Object handle) {
+            this.connectionTrackingInterceptor = connectionTrackingInterceptor;
+            this.connectionInfo = connectionInfo;
+            this.handle = handle;
+        }
+
+        public Object invoke(Object object, Method method, Object[] args) throws Throwable {
+            Object handle;
+            if (method.getDeclaringClass() == Object.class) {
+                if (method.getName().equals("finalize")) {
+                    // ignore the handle will get called if it implemented the method
+                    return null;
+                }
+                if (method.getName().equals("clone")) {
+                    throw new CloneNotSupportedException();
+                }
+                // for equals, hashCode and toString don't activate handle
+                synchronized (this) {
+                    handle = this.handle;
+                }
+            } else {
+                handle = getHandle();
+            }
+            
+            try {
+                Object value = method.invoke(handle, args);
+                return value;
+            } catch (InvocationTargetException ite) {
+                // catch InvocationTargetExceptions and turn them into the target exception (if there is one)
+                Throwable t = ite.getTargetException();
+                if (t != null) {
+                    throw t;
+                }
+                throw ite;
+            }
+
+        }
+
+        public synchronized boolean isReleased() {
+            return released;
+        }
+
+        public synchronized void releaseHandle() {
+            released = true;
+        }
+
+        public synchronized void close() {
+            connectionTrackingInterceptor = null;
+            connectionInfo = null;
+            released = true;
+        }
+
+        public synchronized Object getHandle() {
+            if (connectionTrackingInterceptor == null) {
+                // connection has been closed... send invocations directly to the handle
+                // which will throw an exception or in some clases like JDBC connection.close()
+                // ignore the invocation
+                return handle;
+            }
+
+            if (released) {
+                try {
+                    connectionTrackingInterceptor.reassociateConnection(connectionInfo);
+                } catch (ResourceException e) {
+                    throw (IllegalStateException) new IllegalStateException("Could not obtain a physical connection").initCause(e);
+                }
+                released = false;
+            }
+            return handle;
+        }
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectorInstanceContext.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectorInstanceContext.java
new file mode 100644
index 0000000..578b604
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectorInstanceContext.java
@@ -0,0 +1,36 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectiontracking;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public interface ConnectorInstanceContext {
+    /**
+     * IMPORTANT INVARIANT: this should always return a map, never null.
+     * @return map of ConnectionManager to (list of ) managed connection info objects.
+     */
+    Map getConnectionManagerMap();
+
+    Set getUnshareableResources();
+
+    Set getApplicationManagedSecurityResources();
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectorInstanceContextImpl.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectorInstanceContextImpl.java
new file mode 100644
index 0000000..268c428
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectorInstanceContextImpl.java
@@ -0,0 +1,52 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectiontracking;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Simple implementation of ComponentContext satisfying invariant.
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class ConnectorInstanceContextImpl implements ConnectorInstanceContext {
+    private final Map connectionManagerMap = new HashMap();
+    private final Set unshareableResources;
+    private final Set applicationManagedSecurityResources;
+
+    public ConnectorInstanceContextImpl(Set unshareableResources, Set applicationManagedSecurityResources) {
+        this.unshareableResources = unshareableResources;
+        this.applicationManagedSecurityResources = applicationManagedSecurityResources;
+    }
+
+    public Map getConnectionManagerMap() {
+        return connectionManagerMap;
+    }
+
+    public Set getUnshareableResources() {
+        return unshareableResources;
+    }
+
+    public Set getApplicationManagedSecurityResources() {
+        return applicationManagedSecurityResources;
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/GeronimoTransactionListener.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/GeronimoTransactionListener.java
new file mode 100644
index 0000000..a5d2450
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/GeronimoTransactionListener.java
@@ -0,0 +1,47 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector.outbound.connectiontracking;
+
+import javax.resource.ResourceException;
+import javax.transaction.Transaction;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.geronimo.transaction.manager.TransactionManagerMonitor;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class GeronimoTransactionListener implements TransactionManagerMonitor {
+    private static final Log log = LogFactory.getLog(GeronimoTransactionListener.class);
+    private final TrackedConnectionAssociator trackedConnectionAssociator;
+
+    public GeronimoTransactionListener(TrackedConnectionAssociator trackedConnectionAssociator) {
+        this.trackedConnectionAssociator = trackedConnectionAssociator;
+    }
+
+    public void threadAssociated(Transaction transaction) {
+        try {
+            trackedConnectionAssociator.newTransaction();
+        } catch (ResourceException e) {
+            log.warn("Error notifying connection tranker of transaction association", e);
+        }
+    }
+
+    public void threadUnassociated(Transaction transaction) {
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/SharedConnectorInstanceContext.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/SharedConnectorInstanceContext.java
new file mode 100644
index 0000000..53c0a50
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/SharedConnectorInstanceContext.java
@@ -0,0 +1,66 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector.outbound.connectiontracking;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class SharedConnectorInstanceContext implements ConnectorInstanceContext {
+
+    private Map connectionManagerMap = new HashMap();
+
+    private final Set unshareableResources;
+    private final Set applicationManagedSecurityResources;
+
+    private boolean hide = false;
+
+    public SharedConnectorInstanceContext(Set unshareableResources, Set applicationManagedSecurityResources, boolean share) {
+        this.unshareableResources = unshareableResources;
+        this.applicationManagedSecurityResources = applicationManagedSecurityResources;
+        if (!share) {
+            connectionManagerMap = new HashMap();
+        }
+    }
+
+    public void share(SharedConnectorInstanceContext context) {
+        connectionManagerMap = context.connectionManagerMap;
+    }
+
+    public void hide() {
+        this.hide = true;
+    }
+
+    public Map getConnectionManagerMap() {
+        if (hide) {
+            return Collections.EMPTY_MAP;
+        }
+        return connectionManagerMap;
+    }
+
+    public Set getUnshareableResources() {
+        return unshareableResources;
+    }
+
+    public Set getApplicationManagedSecurityResources() {
+        return applicationManagedSecurityResources;
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/TrackedConnectionAssociator.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/TrackedConnectionAssociator.java
new file mode 100644
index 0000000..970d4ec
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/connectiontracking/TrackedConnectionAssociator.java
@@ -0,0 +1,41 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectiontracking;
+
+import javax.resource.ResourceException;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ */
+public interface TrackedConnectionAssociator {
+    /**
+     * If true, ConnectorInstanceContext instance does not have to be kept on a per component basis; otherwise the
+     * same instance must be passed to enter each time the specific component instance is entered.
+     * @return true if connections are proxied and only connect when invoked
+     */
+    boolean isLazyConnect();
+
+    ConnectorInstanceContext enter(ConnectorInstanceContext newConnectorInstanceContext) throws ResourceException;
+
+    void newTransaction() throws ResourceException;
+
+    void exit(ConnectorInstanceContext connectorInstanceContext) throws ResourceException;
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/security/ResourcePrincipal.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/security/ResourcePrincipal.java
new file mode 100644
index 0000000..32b9e22
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/security/ResourcePrincipal.java
@@ -0,0 +1,57 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.security;
+
+import java.io.Serializable;
+import java.security.Principal;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class ResourcePrincipal implements Principal, Serializable {
+
+    private final String resourcePrincipal;
+
+    public ResourcePrincipal(String resourcePrincipal) {
+        this.resourcePrincipal = resourcePrincipal;
+        if (resourcePrincipal == null) {
+            throw new NullPointerException("No resource principal name supplied");
+        }
+    }
+
+    public String getName() {
+        return resourcePrincipal;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ResourcePrincipal that = (ResourcePrincipal) o;
+
+        return resourcePrincipal.equals(that.resourcePrincipal);
+
+    }
+
+    public int hashCode() {
+        return resourcePrincipal.hashCode();
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/transactionlog/LogXAResource.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/transactionlog/LogXAResource.java
new file mode 100644
index 0000000..4557483
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/transactionlog/LogXAResource.java
@@ -0,0 +1,111 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.transactionlog;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.LocalTransaction;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import org.apache.geronimo.transaction.manager.NamedXAResource;
+
+/**
+ * Works with JDBCLog to provide last resource optimization for a single 1-pc database.
+ * The database work is committed when the log writes its prepare record, not here.
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class LogXAResource implements NamedXAResource {
+
+    final String name;
+    final LocalTransaction localTransaction;
+    private Xid xid;
+
+    public LogXAResource(LocalTransaction localTransaction, String name) {
+        this.localTransaction = localTransaction;
+        this.name = name;
+    }
+    public void commit(Xid xid, boolean onePhase) throws XAException {
+    }
+
+    public void end(Xid xid, int flags) throws XAException {
+    }
+
+    public void forget(Xid xid) throws XAException {
+    }
+
+    public int getTransactionTimeout() throws XAException {
+        return 0;
+    }
+
+    public boolean isSameRM(XAResource xaResource) throws XAException {
+        return this == xaResource;
+    }
+
+    public int prepare(Xid xid) throws XAException {
+        return 0;
+    }
+
+    public Xid[] recover(int flag) throws XAException {
+        return new Xid[0];
+    }
+
+    public void rollback(Xid xid) throws XAException {
+        if (this.xid == null || !this.xid.equals(xid)) {
+            throw new XAException("Invalid Xid");
+        }
+        try {
+            localTransaction.rollback();
+        } catch (ResourceException e) {
+            throw (XAException)new XAException().initCause(e);
+        } finally {
+            this.xid = null;
+        }
+    }
+
+    public boolean setTransactionTimeout(int seconds) throws XAException {
+        return false;
+    }
+
+    public void start(Xid xid, int flag) throws XAException {
+        if (flag == XAResource.TMNOFLAGS) {
+            // first time in this transaction
+            if (this.xid != null) {
+                throw new XAException("already enlisted");
+            }
+            this.xid = xid;
+            try {
+                localTransaction.begin();
+            } catch (ResourceException e) {
+                throw (XAException) new XAException("could not start local tx").initCause(e);
+            }
+        } else if (flag == XAResource.TMRESUME) {
+            if (xid != this.xid) {
+                throw new XAException("attempting to resume in different transaction");
+            }
+        } else {
+            throw new XAException("unknown state");
+        }
+     }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/transactionlog/LogXAResourceInsertionInterceptor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/transactionlog/LogXAResourceInsertionInterceptor.java
new file mode 100644
index 0000000..a3e5850
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/outbound/transactionlog/LogXAResourceInsertionInterceptor.java
@@ -0,0 +1,62 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.transactionlog;
+
+import javax.resource.ResourceException;
+
+import org.apache.geronimo.connector.outbound.ConnectionInfo;
+import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
+import org.apache.geronimo.connector.outbound.ConnectionReturnAction;
+import org.apache.geronimo.connector.outbound.ManagedConnectionInfo;
+
+/**
+ * LocalXAResourceInsertionInterceptor.java
+ *
+ *
+ * @version $Rev$ $Date$
+
+ */
+public class LogXAResourceInsertionInterceptor
+        implements ConnectionInterceptor {
+
+    private final ConnectionInterceptor next;
+    private final String name;
+
+    public LogXAResourceInsertionInterceptor(final ConnectionInterceptor next, String name) {
+        this.next = next;
+        this.name = name;
+    }
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        next.getConnection(connectionInfo);
+        ManagedConnectionInfo mci = connectionInfo.getManagedConnectionInfo();
+        mci.setXAResource(
+                new LogXAResource(
+                        mci.getManagedConnection().getLocalTransaction(), name));
+    }
+
+    public void returnConnection(
+            ConnectionInfo connectionInfo,
+            ConnectionReturnAction connectionReturnAction) {
+        next.returnConnection(connectionInfo, connectionReturnAction);
+    }
+
+    public void destroy() {
+        next.destroy();
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/GeronimoWorkManager.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/GeronimoWorkManager.java
new file mode 100644
index 0000000..e597cca
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/GeronimoWorkManager.java
@@ -0,0 +1,209 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.work;
+
+import java.util.concurrent.Executor;
+
+import javax.resource.spi.work.ExecutionContext;
+import javax.resource.spi.work.Work;
+import javax.resource.spi.work.WorkCompletedException;
+import javax.resource.spi.work.WorkException;
+import javax.resource.spi.work.WorkListener;
+import javax.resource.spi.work.WorkManager;
+
+import org.apache.geronimo.connector.work.pool.ScheduleWorkExecutor;
+import org.apache.geronimo.connector.work.pool.StartWorkExecutor;
+import org.apache.geronimo.connector.work.pool.SyncWorkExecutor;
+import org.apache.geronimo.connector.work.pool.WorkExecutor;
+import org.apache.geronimo.transaction.manager.XAWork;
+
+/**
+ * WorkManager implementation which uses under the cover three WorkExecutorPool
+ * - one for each synchronization policy - in order to dispatch the submitted
+ * Work instances.
+ * <P>
+ * A WorkManager is a component of the JCA specifications, which allows a
+ * Resource Adapter to submit tasks to an Application Server for execution.
+ *
+ * @version $Rev$ $Date$
+ */
+public class GeronimoWorkManager implements WorkManager {
+
+//    private final static int DEFAULT_POOL_SIZE = 10;
+
+    /**
+     * Pool of threads used by this WorkManager in order to process
+     * the Work instances submitted via the doWork methods.
+     */
+    private Executor syncWorkExecutorPool;
+
+    /**
+     * Pool of threads used by this WorkManager in order to process
+     * the Work instances submitted via the startWork methods.
+     */
+    private Executor startWorkExecutorPool;
+
+    /**
+     * Pool of threads used by this WorkManager in order to process
+     * the Work instances submitted via the scheduleWork methods.
+     */
+    private Executor scheduledWorkExecutorPool;
+
+    private final XAWork transactionManager;
+
+    private final WorkExecutor scheduleWorkExecutor = new ScheduleWorkExecutor();
+    private final WorkExecutor startWorkExecutor = new StartWorkExecutor();
+    private final WorkExecutor syncWorkExecutor = new SyncWorkExecutor();
+
+    /**
+     * Create a WorkManager.
+     */
+    public GeronimoWorkManager() {
+        this(null, null, null, null);
+    }
+
+    public GeronimoWorkManager(Executor sync, Executor start, Executor sched, XAWork xaWork) {
+        syncWorkExecutorPool = sync;
+        startWorkExecutorPool = start;
+        scheduledWorkExecutorPool = sched;
+        this.transactionManager = xaWork;
+    }
+
+    public void doStart() throws Exception {
+    }
+
+    public void doStop() throws Exception {
+    }
+
+    public void doFail() {
+        try {
+            doStop();
+        } catch (Exception e) {
+            //TODO what to do?
+        }
+    }
+
+    public Executor getSyncWorkExecutorPool() {
+        return syncWorkExecutorPool;
+    }
+
+    public Executor getStartWorkExecutorPool() {
+        return startWorkExecutorPool;
+    }
+
+    public Executor getScheduledWorkExecutorPool() {
+        return scheduledWorkExecutorPool;
+    }
+
+    /* (non-Javadoc)
+    * @see javax.resource.spi.work.WorkManager#doWork(javax.resource.spi.work.Work)
+    */
+    public void doWork(Work work) throws WorkException {
+        executeWork(new WorkerContext(work, transactionManager), syncWorkExecutor, syncWorkExecutorPool);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.resource.spi.work.WorkManager#doWork(javax.resource.spi.work.Work, long, javax.resource.spi.work.ExecutionContext, javax.resource.spi.work.WorkListener)
+     */
+    public void doWork(
+            Work work,
+            long startTimeout,
+            ExecutionContext execContext,
+            WorkListener workListener)
+            throws WorkException {
+        WorkerContext workWrapper =
+                new WorkerContext(work, startTimeout, execContext, transactionManager, workListener);
+        workWrapper.setThreadPriority(Thread.currentThread().getPriority());
+        executeWork(workWrapper, syncWorkExecutor, syncWorkExecutorPool);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.resource.spi.work.WorkManager#startWork(javax.resource.spi.work.Work)
+     */
+    public long startWork(Work work) throws WorkException {
+        WorkerContext workWrapper = new WorkerContext(work, transactionManager);
+        workWrapper.setThreadPriority(Thread.currentThread().getPriority());
+        executeWork(workWrapper, startWorkExecutor, startWorkExecutorPool);
+        return System.currentTimeMillis() - workWrapper.getAcceptedTime();
+    }
+
+    /* (non-Javadoc)
+     * @see javax.resource.spi.work.WorkManager#startWork(javax.resource.spi.work.Work, long, javax.resource.spi.work.ExecutionContext, javax.resource.spi.work.WorkListener)
+     */
+    public long startWork(
+            Work work,
+            long startTimeout,
+            ExecutionContext execContext,
+            WorkListener workListener)
+            throws WorkException {
+        WorkerContext workWrapper =
+                new WorkerContext(work, startTimeout, execContext, transactionManager, workListener);
+        workWrapper.setThreadPriority(Thread.currentThread().getPriority());
+        executeWork(workWrapper, startWorkExecutor, startWorkExecutorPool);
+        return System.currentTimeMillis() - workWrapper.getAcceptedTime();
+    }
+
+    /* (non-Javadoc)
+     * @see javax.resource.spi.work.WorkManager#scheduleWork(javax.resource.spi.work.Work)
+     */
+    public void scheduleWork(Work work) throws WorkException {
+        WorkerContext workWrapper = new WorkerContext(work, transactionManager);
+        workWrapper.setThreadPriority(Thread.currentThread().getPriority());
+        executeWork(workWrapper, scheduleWorkExecutor, scheduledWorkExecutorPool);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.resource.spi.work.WorkManager#scheduleWork(javax.resource.spi.work.Work, long, javax.resource.spi.work.ExecutionContext, javax.resource.spi.work.WorkListener)
+     */
+    public void scheduleWork(
+            Work work,
+            long startTimeout,
+            ExecutionContext execContext,
+            WorkListener workListener)
+            throws WorkException {
+        WorkerContext workWrapper =
+                new WorkerContext(work, startTimeout, execContext, transactionManager, workListener);
+        workWrapper.setThreadPriority(Thread.currentThread().getPriority());
+        executeWork(workWrapper, scheduleWorkExecutor, scheduledWorkExecutorPool);
+    }
+
+    /**
+     * Execute the specified Work.
+     *
+     * @param work Work to be executed.
+     *
+     * @exception WorkException Indicates that the Work execution has been
+     * unsuccessful.
+     */
+    private void executeWork(WorkerContext work, WorkExecutor workExecutor, Executor pooledExecutor) throws WorkException {
+        work.workAccepted(this);
+        try {
+            workExecutor.doExecute(work, pooledExecutor);
+            WorkException exception = work.getWorkException();
+            if (null != exception) {
+                throw exception;
+            }
+        } catch (InterruptedException e) {
+            WorkCompletedException wcj = new WorkCompletedException(
+                    "The execution has been interrupted.", e);
+            wcj.setErrorCode(WorkException.INTERNAL);
+            throw wcj;
+        }
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/WorkerContext.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/WorkerContext.java
new file mode 100644
index 0000000..ecd9968
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/WorkerContext.java
@@ -0,0 +1,348 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.work;
+
+import java.util.concurrent.CountDownLatch;
+
+import javax.resource.spi.work.ExecutionContext;
+import javax.resource.spi.work.Work;
+import javax.resource.spi.work.WorkAdapter;
+import javax.resource.spi.work.WorkCompletedException;
+import javax.resource.spi.work.WorkEvent;
+import javax.resource.spi.work.WorkException;
+import javax.resource.spi.work.WorkListener;
+import javax.resource.spi.work.WorkManager;
+import javax.resource.spi.work.WorkRejectedException;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.SystemException;
+import javax.transaction.xa.XAException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.geronimo.transaction.manager.ImportedTransactionActiveException;
+import org.apache.geronimo.transaction.manager.XAWork;
+
+/**
+ * Work wrapper providing an execution context to a Work instance.
+ *
+ * @version $Rev$ $Date$
+ */
+public class WorkerContext implements Work {
+
+    private static final Log log = LogFactory.getLog(WorkerContext.class);
+
+    /**
+     * Null WorkListener used as the default WorkListener.
+     */
+    private static final WorkListener NULL_WORK_LISTENER = new WorkAdapter() {
+        public void workRejected(WorkEvent event) {
+            if (event.getException() != null) {
+                if (event.getException() instanceof WorkCompletedException && event.getException().getCause() != null) {
+                    log.error(event.getWork().toString(), event.getException().getCause());
+                } else {
+                    log.error(event.getWork().toString(), event.getException());
+                }
+            }
+        }
+    };
+
+    /**
+     * Priority of the thread, which will execute this work.
+     */
+    private int threadPriority;
+
+    /**
+     * Actual work to be executed.
+     */
+    private Work adaptee;
+
+    /**
+     * Indicates if this work has been accepted.
+     */
+    private boolean isAccepted;
+
+    /**
+     * System.currentTimeMillis() when the wrapped Work has been accepted.
+     */
+    private long acceptedTime;
+
+    /**
+     * Number of times that the execution of this work has been tried.
+     */
+    private int nbRetry;
+
+    /**
+     * Time duration (in milliseconds) within which the execution of the Work
+     * instance must start.
+     */
+    private long startTimeOut;
+
+    /**
+     * Execution context of the actual work to be executed.
+     */
+    private final ExecutionContext executionContext;
+
+    private final XAWork xaWork;
+
+    /**
+     * Listener to be notified during the life-cycle of the work treatment.
+     */
+    private WorkListener workListener = NULL_WORK_LISTENER;
+
+    /**
+     * Work exception, if any.
+     */
+    private WorkException workException;
+
+    /**
+     * A latch, which is released when the work is started.
+     */
+    private CountDownLatch startLatch = new CountDownLatch(1);
+
+    /**
+     * A latch, which is released when the work is completed.
+     */
+    private CountDownLatch endLatch = new CountDownLatch(1);
+
+    /**
+     * Create a WorkWrapper.
+     *
+     * @param work                      Work to be wrapped.
+     * @param xaWork
+     */
+    public WorkerContext(Work work, XAWork xaWork) {
+        adaptee = work;
+        executionContext = null;
+        this.xaWork = xaWork;
+    }
+
+    /**
+     * Create a WorkWrapper with the specified execution context.
+     *
+     * @param aWork         Work to be wrapped.
+     * @param aStartTimeout a time duration (in milliseconds) within which the
+     *                      execution of the Work instance must start.
+     * @param execContext   an object containing the execution context with which
+     *                      the submitted Work instance must be executed.
+     * @param workListener  an object which would be notified when the various
+     *                      Work processing events (work accepted, work rejected, work started,
+     */
+    public WorkerContext(Work aWork,
+                         long aStartTimeout,
+                         ExecutionContext execContext,
+                         XAWork xaWork,
+                         WorkListener workListener) {
+        adaptee = aWork;
+        startTimeOut = aStartTimeout;
+        executionContext = execContext;
+        this.xaWork = xaWork;
+        if (null != workListener) {
+            this.workListener = workListener;
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see javax.resource.spi.work.Work#release()
+     */
+    public void release() {
+        adaptee.release();
+    }
+
+    /**
+     * Defines the thread priority level of the thread, which will be dispatched
+     * to process this work. This priority level must be the same one for a
+     * given resource adapter.
+     *
+     * @param aPriority Priority of the thread to be used to process the wrapped
+     *                  Work instance.
+     */
+    public void setThreadPriority(int aPriority) {
+        threadPriority = aPriority;
+    }
+
+    /**
+     * Gets the priority level of the thread, which will be dispatched
+     * to process this work. This priority level must be the same one for a
+     * given resource adapter.
+     *
+     * @return The priority level of the thread to be dispatched to
+     *         process the wrapped Work instance.
+     */
+    public int getThreadPriority() {
+        return threadPriority;
+    }
+
+    /**
+     * Call-back method used by a Work executor in order to notify this
+     * instance that the wrapped Work instance has been accepted.
+     *
+     * @param anObject Object on which the event initially occurred. It should
+     *                 be the work executor.
+     */
+    public synchronized void workAccepted(Object anObject) {
+        isAccepted = true;
+        acceptedTime = System.currentTimeMillis();
+        workListener.workAccepted(new WorkEvent(anObject,
+                WorkEvent.WORK_ACCEPTED, adaptee, null));
+    }
+
+    /**
+     * System.currentTimeMillis() when the Work has been accepted. This method
+     * can be used to compute the duration of a work.
+     *
+     * @return When the work has been accepted.
+     */
+    public synchronized long getAcceptedTime() {
+        return acceptedTime;
+    }
+
+    /**
+     * Gets the time duration (in milliseconds) within which the execution of
+     * the Work instance must start.
+     *
+     * @return Time out duration.
+     */
+    public long getStartTimeout() {
+        return startTimeOut;
+    }
+
+    /**
+     * Used by a Work executor in order to know if this work, which should be
+     * accepted but not started has timed out. This method MUST be called prior
+     * to retry the execution of a Work.
+     *
+     * @return true if the Work has timed out and false otherwise.
+     */
+    public synchronized boolean isTimedOut() {
+        assert isAccepted: "The work is not accepted.";
+        // A value of 0 means that the work never times out.
+        //??? really?
+        if (0 == startTimeOut || startTimeOut == WorkManager.INDEFINITE) {
+            return false;
+        }
+        boolean isTimeout = acceptedTime + startTimeOut > 0 &&
+                System.currentTimeMillis() > acceptedTime + startTimeOut;
+        if (log.isDebugEnabled()) {
+            log.debug(this
+                    + " accepted at "
+                    + acceptedTime
+                    + (isTimeout ? " has timed out." : " has not timed out. ")
+                    + nbRetry
+                    + " retries have been performed.");
+        }
+        if (isTimeout) {
+            workException = new WorkRejectedException(this + " has timed out.",
+                    WorkException.START_TIMED_OUT);
+            workListener.workRejected(new WorkEvent(this,
+                    WorkEvent.WORK_REJECTED,
+                    adaptee,
+                    workException));
+            return true;
+        }
+        nbRetry++;
+        return isTimeout;
+    }
+
+    /**
+     * Gets the WorkException, if any, thrown during the execution.
+     *
+     * @return WorkException, if any.
+     */
+    public synchronized WorkException getWorkException() {
+        return workException;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Runnable#run()
+     */
+    public void run() {
+        if (isTimedOut()) {
+            // In case of a time out, one releases the start and end latches
+            // to prevent a dead-lock.
+            startLatch.countDown();
+            endLatch.countDown();
+            return;
+        }
+        // Implementation note: the work listener is notified prior to release
+        // the start lock. This behavior is intentional and seems to be the
+        // more conservative.
+        workListener.workStarted(new WorkEvent(this, WorkEvent.WORK_STARTED, adaptee, null));
+        startLatch.countDown();
+        //Implementation note: we assume this is being called without an interesting TransactionContext,
+        //and ignore/replace whatever is associated with the current thread.
+        try {
+            if (executionContext == null || executionContext.getXid() == null) {
+                adaptee.run();
+            } else {
+                try {
+                    long transactionTimeout = executionContext.getTransactionTimeout();
+                    //translate -1 value to 0 to indicate default transaction timeout.
+                    xaWork.begin(executionContext.getXid(), transactionTimeout < 0 ? 0 : transactionTimeout);
+                } catch (XAException e) {
+                    throw new WorkCompletedException("Transaction import failed for xid " + executionContext.getXid(), WorkCompletedException.TX_RECREATE_FAILED).initCause(e);
+                } catch (InvalidTransactionException e) {
+                    throw new WorkCompletedException("Transaction import failed for xid " + executionContext.getXid(), WorkCompletedException.TX_RECREATE_FAILED).initCause(e);
+                } catch (SystemException e) {
+                    throw new WorkCompletedException("Transaction import failed for xid " + executionContext.getXid(), WorkCompletedException.TX_RECREATE_FAILED).initCause(e);
+                } catch (ImportedTransactionActiveException e) {
+                    throw new WorkCompletedException("Transaction already active for xid " + executionContext.getXid(), WorkCompletedException.TX_CONCURRENT_WORK_DISALLOWED).initCause(e);
+                }
+                try {
+                    adaptee.run();
+                } finally {
+                    xaWork.end(executionContext.getXid());
+                }
+
+            }
+            workListener.workCompleted(new WorkEvent(this, WorkEvent.WORK_COMPLETED, adaptee, null));
+        } catch (Throwable e) {
+            workException = (WorkException) (e instanceof WorkCompletedException ? e : new WorkCompletedException("Unknown error", WorkCompletedException.UNDEFINED).initCause(e));
+            workListener.workCompleted(new WorkEvent(this, WorkEvent.WORK_REJECTED, adaptee,
+                    workException));
+        } finally {
+            endLatch.countDown();
+        }
+    }
+
+    /**
+     * Provides a latch, which can be used to wait the start of a work
+     * execution.
+     *
+     * @return Latch that a caller can acquire to wait for the start of a
+     *         work execution.
+     */
+    public synchronized CountDownLatch provideStartLatch() {
+        return startLatch;
+    }
+
+    /**
+     * Provides a latch, which can be used to wait the end of a work
+     * execution.
+     *
+     * @return Latch that a caller can acquire to wait for the end of a
+     *         work execution.
+     */
+    public synchronized CountDownLatch provideEndLatch() {
+        return endLatch;
+    }
+
+    public String toString() {
+        return "Work :" + adaptee;
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/NamedRunnable.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/NamedRunnable.java
new file mode 100644
index 0000000..31c5b4e
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/NamedRunnable.java
@@ -0,0 +1,38 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector.work.pool;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class NamedRunnable implements Runnable {
+    private final String name;
+    private final Runnable runnable;
+
+    public NamedRunnable(String name, Runnable runnable) {
+        this.name = name;
+        this.runnable = runnable;
+    }
+
+    public void run() {
+        runnable.run();
+    }
+
+    public String toString() {
+        return name;
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/NullWorkExecutorPool.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/NullWorkExecutorPool.java
new file mode 100644
index 0000000..03925b9
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/NullWorkExecutorPool.java
@@ -0,0 +1,57 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.work.pool;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class NullWorkExecutorPool implements WorkExecutorPool {
+
+    private int maxSize;
+
+    public NullWorkExecutorPool(int maxSize) {
+        this.maxSize = maxSize;
+    }
+
+    public int getPoolSize() {
+        return 0;
+    }
+
+    public int getMaximumPoolSize() {
+        return maxSize;
+    }
+
+    public void setMaximumPoolSize(int maxSize) {
+        this.maxSize = maxSize;
+    }
+
+    public WorkExecutorPool start() {
+        return new WorkExecutorPoolImpl(maxSize);
+    }
+
+    public WorkExecutorPool stop() {
+        return this;
+    }
+
+    public void execute(Runnable command) {
+        throw new IllegalStateException("Stopped");
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/ScheduleWorkExecutor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/ScheduleWorkExecutor.java
new file mode 100644
index 0000000..603acb7
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/ScheduleWorkExecutor.java
@@ -0,0 +1,38 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.work.pool;
+
+import java.util.concurrent.Executor;
+
+import javax.resource.spi.work.WorkException;
+
+import org.apache.geronimo.connector.work.WorkerContext;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class ScheduleWorkExecutor implements WorkExecutor {
+
+    public void doExecute(WorkerContext work, Executor executor)
+            throws WorkException, InterruptedException {
+        executor.execute(new NamedRunnable("A J2EE Connector", work));
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/StartWorkExecutor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/StartWorkExecutor.java
new file mode 100644
index 0000000..d3df8e9
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/StartWorkExecutor.java
@@ -0,0 +1,41 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.work.pool;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+
+import javax.resource.spi.work.WorkException;
+
+import org.apache.geronimo.connector.work.WorkerContext;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class StartWorkExecutor implements WorkExecutor {
+
+    public void doExecute(WorkerContext work, Executor executor)
+            throws WorkException, InterruptedException {
+        CountDownLatch latch = work.provideStartLatch();
+        executor.execute(new NamedRunnable("A J2EE Connector", work));
+        latch.await();
+    }
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/SyncWorkExecutor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/SyncWorkExecutor.java
new file mode 100644
index 0000000..2933eec
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/SyncWorkExecutor.java
@@ -0,0 +1,42 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.work.pool;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+
+import javax.resource.spi.work.WorkException;
+
+import org.apache.geronimo.connector.work.WorkerContext;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class SyncWorkExecutor implements WorkExecutor {
+
+    public void doExecute(WorkerContext work, Executor executor)
+            throws WorkException, InterruptedException {
+        CountDownLatch latch = work.provideEndLatch();
+        executor.execute(new NamedRunnable("A J2EE Connector", work));
+        latch.await();
+    }
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/WorkExecutor.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/WorkExecutor.java
new file mode 100644
index 0000000..3efbe88
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/WorkExecutor.java
@@ -0,0 +1,48 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector.work.pool;
+
+import java.util.concurrent.Executor;
+
+import javax.resource.spi.work.WorkException;
+
+import org.apache.geronimo.connector.work.WorkerContext;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public interface WorkExecutor {
+
+    /**
+     * This method must be implemented by sub-classes in order to provide the
+     * relevant synchronization policy. It is called by the executeWork template
+     * method.
+     *
+     * @param work Work to be executed.
+     *
+     * @throws javax.resource.spi.work.WorkException Indicates that the work has failed.
+     * @throws InterruptedException Indicates that the thread in charge of the
+     * execution of the specified work has been interrupted.
+     */
+     void doExecute(WorkerContext work, Executor executor)
+            throws WorkException, InterruptedException;
+
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/WorkExecutorPool.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/WorkExecutorPool.java
new file mode 100644
index 0000000..115ce35
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/WorkExecutorPool.java
@@ -0,0 +1,55 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.work.pool;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Defines the operations that a pool in charge of the execution of Work
+ * instances must expose.
+ *
+ * @version $Rev$ $Date$
+ */
+public interface WorkExecutorPool extends Executor {
+
+    /**
+     * Gets the current number of active threads in the pool.
+     *
+     * @return Number of active threads in the pool.
+     */
+    public int getPoolSize();
+
+    /**
+     * Gets the maximum number of threads to simultaneously execute.
+     *
+     * @return Maximum size.
+     */
+    public int getMaximumPoolSize();
+
+    /**
+     * Sets the maximum number of threads to simultaneously execute.
+     *
+     * @param aSize Maximum size.
+     */
+    public void setMaximumPoolSize(int aSize);
+
+    public WorkExecutorPool start();
+
+    public WorkExecutorPool stop();
+
+}
diff --git a/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/WorkExecutorPoolImpl.java b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/WorkExecutorPoolImpl.java
new file mode 100644
index 0000000..199f897
--- /dev/null
+++ b/geronimo-connector/src/main/java/org/apache/geronimo/connector/work/pool/WorkExecutorPoolImpl.java
@@ -0,0 +1,106 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.work.pool;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Based class for WorkExecutorPool. Sub-classes define the synchronization
+ * policy (should the call block until the end of the work; or when it starts
+ * et cetera).
+ *
+ * @version $Rev$ $Date$
+ */
+public class WorkExecutorPoolImpl implements WorkExecutorPool {
+
+    /**
+     * A timed out pooled executor.
+     */
+    private ThreadPoolExecutor pooledExecutor;
+    private static Log log = LogFactory.getLog(WorkExecutorPoolImpl.class);
+
+    /**
+     * Creates a pool with the specified minimum and maximum sizes. The Channel
+     * used to enqueue the submitted Work instances is queueless synchronous
+     * one.
+     *
+     * @param maxSize Maximum size of the work executor pool.
+     */
+    public WorkExecutorPoolImpl(int maxSize) {
+        pooledExecutor = new ThreadPoolExecutor(1, maxSize, 60, TimeUnit.SECONDS, new LinkedBlockingQueue());
+        /*
+        FIXME: How to do this with concurrent.util ?
+        pooledExecutor.waitWhenBlocked();
+        */
+    }
+    
+    /**
+     * Execute the specified Work.
+     *
+     * @param work Work to be executed.
+     */
+    public void execute(Runnable work) {
+        if(pooledExecutor.getPoolSize() == pooledExecutor.getMaximumPoolSize()) {
+            log.warn("Maximum Pool size has been exceeded.  Current Pool Size = "+pooledExecutor.getMaximumPoolSize());
+        }
+
+        pooledExecutor.execute(work);
+    }
+
+    /**
+     * Gets the size of this pool.
+     */
+    public int getPoolSize() {
+        return pooledExecutor.getPoolSize();
+    }
+
+    /**
+     * Gets the maximum size of this pool.
+     */
+    public int getMaximumPoolSize() {
+        return pooledExecutor.getMaximumPoolSize();
+    }
+
+    /**
+     * Sets the maximum size of this pool.
+     * @param maxSize New maximum size of this pool.
+     */
+    public void setMaximumPoolSize(int maxSize) {
+        pooledExecutor.setMaximumPoolSize(maxSize);
+    }
+
+    public WorkExecutorPool start() {
+        throw new IllegalStateException("This pooled executor is already started");
+    }
+
+    /**
+     * Stops this pool. Prior to stop this pool, all the enqueued Work instances
+     * are processed. This is an orderly shutdown.
+     */
+    public WorkExecutorPool stop() {
+        int maxSize = getMaximumPoolSize();
+        pooledExecutor.shutdown();
+        return new NullWorkExecutorPool(maxSize);
+    }
+
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/BootstrapContextTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/BootstrapContextTest.java
new file mode 100644
index 0000000..0900f7b
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/BootstrapContextTest.java
@@ -0,0 +1,125 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector;
+
+import java.util.Timer;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import javax.resource.spi.XATerminator;
+import javax.resource.spi.work.WorkManager;
+
+import junit.framework.TestCase;
+import org.apache.geronimo.connector.work.GeronimoWorkManager;
+import org.apache.geronimo.transaction.manager.GeronimoTransactionManager;
+import org.apache.geronimo.transaction.manager.XAWork;
+
+/**
+ * Unit tests for {@link GeronimoBootstrapContext}
+ * @version $Rev$ $Date$
+ */
+public class BootstrapContextTest extends TestCase {
+    ThreadPoolExecutor pool;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        XAWork xaWork = new GeronimoTransactionManager();
+        int poolSize = 1;
+        int keepAliveTime = 30000;
+        ThreadPoolExecutor pool = new ThreadPoolExecutor(
+            poolSize, // core size
+            poolSize, // max size
+            keepAliveTime, TimeUnit.MILLISECONDS,
+            new SynchronousQueue());
+
+        pool.setRejectedExecutionHandler(new WaitWhenBlockedPolicy());
+        pool.setThreadFactory(new ThreadPoolThreadFactory("Connector Test", getClass().getClassLoader()));
+    }
+
+    private static class WaitWhenBlockedPolicy
+        implements RejectedExecutionHandler {
+        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) throws RejectedExecutionException {
+            try {
+                executor.getQueue().put(r);
+            }
+            catch (InterruptedException e) {
+                throw new RejectedExecutionException(e);
+            }
+        }
+    }
+    private static final class ThreadPoolThreadFactory implements ThreadFactory {
+        private final String poolName;
+        private final ClassLoader classLoader;
+
+        private int nextWorkerID = 0;
+
+        public ThreadPoolThreadFactory(String poolName, ClassLoader classLoader) {
+            this.poolName = poolName;
+            this.classLoader = classLoader;
+        }
+
+        public Thread newThread(Runnable arg0) {
+            Thread thread = new Thread(arg0, poolName + " " + getNextWorkerID());
+            thread.setContextClassLoader(classLoader);
+            return thread;
+        }
+
+        private synchronized int getNextWorkerID() {
+            return nextWorkerID++;
+        }
+    }
+
+    /**
+     * Tests get and set work manager
+     */
+    public void testGetSetWorkManager() throws Exception {
+        GeronimoTransactionManager transactionManager = new GeronimoTransactionManager();
+        GeronimoWorkManager manager = new GeronimoWorkManager(pool, pool, pool, transactionManager);
+        GeronimoBootstrapContext context = new GeronimoBootstrapContext(manager, transactionManager);
+        WorkManager wm = context.getWorkManager();
+
+        assertSame("Make sure it is the same object", manager, wm);
+    }
+
+    /**
+     * Tests get and set XATerminator
+     */
+    public void testGetSetXATerminator() throws Exception {
+        GeronimoTransactionManager transactionManager = new GeronimoTransactionManager();
+        GeronimoWorkManager manager = new GeronimoWorkManager(pool, pool, pool, transactionManager);
+        GeronimoBootstrapContext context = new GeronimoBootstrapContext(manager, transactionManager);
+        XATerminator xat = context.getXATerminator();
+
+        assertSame("Make sure it is the same object", transactionManager, xat);
+    }
+
+    /**
+     * Tests getTimer
+     */
+    public void testGetTimer() throws Exception {
+        GeronimoBootstrapContext context = new GeronimoBootstrapContext(null, null);
+        Timer t = context.createTimer();
+        assertNotNull("Object is not null", t);
+    }
+
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/MockTransaction.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/MockTransaction.java
new file mode 100644
index 0000000..517a257
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/MockTransaction.java
@@ -0,0 +1,82 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector;
+
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.RollbackException;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.xa.XAResource;
+
+/**
+ * Dummy implementation of Transaction interface for use in
+ * {@link TxUtilsTest}
+ * @version $Rev$ $Date$
+ */
+public class MockTransaction implements Transaction {
+
+    private int status = -1;
+
+    /** Creates a new instance of MockWorkManager */
+    public MockTransaction() {
+    }
+
+    public void commit() throws HeuristicMixedException,
+            HeuristicRollbackException,
+            RollbackException,
+            SecurityException,
+            SystemException {
+    }
+
+    public boolean delistResource(XAResource xaRes, int flag)
+            throws IllegalStateException, SystemException {
+        return false;
+    }
+
+    public boolean enlistResource(XAResource xaRes)
+            throws IllegalStateException,
+            RollbackException,
+            SystemException {
+        return false;
+    }
+
+    public int getStatus() throws SystemException {
+        return status;
+    }
+
+    public void registerSynchronization(Synchronization synch)
+            throws IllegalStateException,
+            RollbackException,
+            SystemException {
+    }
+
+    public void rollback() throws IllegalStateException, SystemException {
+    }
+
+    public void setRollbackOnly()
+            throws IllegalStateException,
+            SystemException {
+    }
+
+    public void setStatus(int status) {
+        this.status = status;
+    }
+
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/MockWorkManager.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/MockWorkManager.java
new file mode 100644
index 0000000..fc46d94
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/MockWorkManager.java
@@ -0,0 +1,86 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector;
+
+import javax.resource.spi.work.ExecutionContext;
+import javax.resource.spi.work.Work;
+import javax.resource.spi.work.WorkException;
+import javax.resource.spi.work.WorkListener;
+import javax.resource.spi.work.WorkManager;
+
+/**
+ * Dummy implementation of WorkManager interface for use in
+ * {@link BootstrapContextTest}
+ * @version $Rev$ $Date$
+ */
+public class MockWorkManager
+        implements WorkManager {
+
+    private String id = null;
+
+    /** Creates a new instance of MockWorkManager */
+    public MockWorkManager(String id) {
+        this.id = id;
+    }
+
+    public void doWork(Work work) throws WorkException {
+    }
+
+    public void doWork(Work work,
+            long startTimeout,
+            ExecutionContext execContext,
+            WorkListener workListener)
+            throws WorkException {
+    }
+
+    public void scheduleWork(Work work) throws WorkException {
+    }
+
+    public void scheduleWork(Work work,
+            long startTimeout,
+            ExecutionContext execContext,
+            WorkListener workListener)
+            throws WorkException {
+    }
+
+    public long startWork(Work work) throws WorkException {
+        return -1;
+    }
+
+    public long startWork(Work work,
+            long startTimeout,
+            ExecutionContext execContext,
+            WorkListener workListener)
+            throws WorkException {
+        return -1;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public boolean equals(WorkManager wm) {
+        if (!(wm instanceof MockWorkManager)) {
+            return false;
+        }
+
+        return ((MockWorkManager) wm).getId() != null &&
+                ((MockWorkManager) wm).getId().equals(getId());
+    }
+
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/ConnectionExtension.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/ConnectionExtension.java
new file mode 100644
index 0000000..44ada99
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/ConnectionExtension.java
@@ -0,0 +1,37 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector.mock;
+
+import javax.resource.cci.Connection;
+import javax.security.auth.Subject;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public interface ConnectionExtension extends Connection {
+    void error();
+
+    MockManagedConnection getManagedConnection();
+
+    Subject getSubject();
+
+    MockConnectionRequestInfo getConnectionRequestInfo();
+
+    boolean isClosed();
+
+    void reassociate(MockManagedConnection mockManagedConnection);
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/ConnectionFactoryExtension.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/ConnectionFactoryExtension.java
new file mode 100644
index 0000000..d1965ea
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/ConnectionFactoryExtension.java
@@ -0,0 +1,30 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector.mock;
+
+import javax.resource.cci.ConnectionFactory;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public interface ConnectionFactoryExtension extends ConnectionFactory{
+
+    String doSomethingElse();
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockActivationSpec.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockActivationSpec.java
new file mode 100644
index 0000000..bed67f2
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockActivationSpec.java
@@ -0,0 +1,41 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.mock;
+
+import javax.resource.spi.ActivationSpec;
+import javax.resource.spi.InvalidPropertyException;
+import javax.resource.spi.ResourceAdapter;
+import javax.resource.ResourceException;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class MockActivationSpec implements ActivationSpec {
+    public void validate() throws InvalidPropertyException {
+    }
+
+    public ResourceAdapter getResourceAdapter() {
+        return null;
+    }
+
+    public void setResourceAdapter(ResourceAdapter ra) throws ResourceException {
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockAdminObject.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockAdminObject.java
new file mode 100644
index 0000000..a9c3094
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockAdminObject.java
@@ -0,0 +1,36 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.mock;
+
+import java.io.Serializable;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public interface MockAdminObject extends Serializable {
+
+    String getTweedle();
+
+    void setTweedle(String tweedle);
+
+    MockAdminObject getSomething();
+
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockAdminObjectImpl.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockAdminObjectImpl.java
new file mode 100644
index 0000000..42e7e0b
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockAdminObjectImpl.java
@@ -0,0 +1,41 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.mock;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class MockAdminObjectImpl implements MockAdminObject {
+
+    private String tweedle;
+
+    public String getTweedle() {
+        return tweedle;
+    }
+
+    public void setTweedle(String tweedle) {
+        this.tweedle = tweedle;
+    }
+
+    public MockAdminObject getSomething() {
+        return this;
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockCCILocalTransaction.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockCCILocalTransaction.java
new file mode 100644
index 0000000..6359f9e
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockCCILocalTransaction.java
@@ -0,0 +1,51 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.mock;
+
+import javax.resource.ResourceException;
+import javax.resource.cci.LocalTransaction;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class MockCCILocalTransaction extends MockSPILocalTransaction implements LocalTransaction {
+
+    private final MockConnection mockConnection;
+
+    public MockCCILocalTransaction(MockConnection mockConnection) {
+        this.mockConnection = mockConnection;
+    }
+
+    public void begin() throws ResourceException {
+        super.begin();
+        mockConnection.getManagedConnection().localTransactionStartedEvent(mockConnection);
+    }
+
+    public void commit() throws ResourceException {
+        super.commit();
+        mockConnection.getManagedConnection().localTransactionCommittedEvent(mockConnection);
+    }
+
+    public void rollback() throws ResourceException {
+        super.rollback();
+        mockConnection.getManagedConnection().localTransactionRolledBackEvent(mockConnection);
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockConnection.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockConnection.java
new file mode 100644
index 0000000..3153996
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockConnection.java
@@ -0,0 +1,97 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.mock;
+
+import javax.resource.ResourceException;
+import javax.resource.cci.ConnectionMetaData;
+import javax.resource.cci.Interaction;
+import javax.resource.cci.LocalTransaction;
+import javax.resource.cci.ResultSetInfo;
+import javax.security.auth.Subject;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class MockConnection implements ConnectionExtension {
+
+    private MockManagedConnection managedConnection;
+    private Subject subject;
+    private MockConnectionRequestInfo connectionRequestInfo;
+
+    private boolean closed;
+
+
+    public MockConnection(MockManagedConnection managedConnection, Subject subject, MockConnectionRequestInfo connectionRequestInfo) {
+        this.managedConnection = managedConnection;
+        this.subject = subject;
+        this.connectionRequestInfo = connectionRequestInfo;
+    }
+
+    public Interaction createInteraction() throws ResourceException {
+        return null;
+    }
+
+    public LocalTransaction getLocalTransaction() throws ResourceException {
+        return new MockCCILocalTransaction(this);
+    }
+
+    public ConnectionMetaData getMetaData() throws ResourceException {
+        return null;
+    }
+
+    public ResultSetInfo getResultSetInfo() throws ResourceException {
+        return null;
+    }
+
+    public void close() throws ResourceException {
+        closed = true;
+        managedConnection.removeHandle(this);
+        managedConnection.closedEvent(this);
+    }
+
+    public void error() {
+        managedConnection.errorEvent(this);
+    }
+
+    public MockManagedConnection getManagedConnection() {
+        return managedConnection;
+    }
+
+    public Subject getSubject() {
+        return subject;
+    }
+
+    public MockConnectionRequestInfo getConnectionRequestInfo() {
+        return connectionRequestInfo;
+    }
+
+    public boolean isClosed() {
+        return closed;
+    }
+
+    public void reassociate(MockManagedConnection mockManagedConnection) {
+        assert managedConnection != null;
+        managedConnection.removeHandle(this);
+        managedConnection = mockManagedConnection;
+        subject = mockManagedConnection.getSubject();
+        connectionRequestInfo = mockManagedConnection.getConnectionRequestInfo();
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockConnectionFactory.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockConnectionFactory.java
new file mode 100644
index 0000000..a63af59
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockConnectionFactory.java
@@ -0,0 +1,70 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.mock;
+
+import javax.naming.NamingException;
+import javax.naming.Reference;
+import javax.resource.ResourceException;
+import javax.resource.cci.Connection;
+import javax.resource.cci.ConnectionSpec;
+import javax.resource.cci.RecordFactory;
+import javax.resource.cci.ResourceAdapterMetaData;
+import javax.resource.spi.ConnectionManager;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class MockConnectionFactory implements ConnectionFactoryExtension {
+
+    private ConnectionManager connectionManager;
+    private MockManagedConnectionFactory managedConnectionFactory;
+    private Reference reference;
+
+    public MockConnectionFactory(MockManagedConnectionFactory managedConnectionFactory, ConnectionManager connectionManager) {
+        this.managedConnectionFactory = managedConnectionFactory;
+        this.connectionManager = connectionManager;
+    }
+
+    public Connection getConnection() throws ResourceException {
+        return getConnection(null);
+    }
+
+    public Connection getConnection(ConnectionSpec properties) throws ResourceException {
+        return (Connection) connectionManager.allocateConnection(managedConnectionFactory, (MockConnectionRequestInfo) properties);
+    }
+
+    public RecordFactory getRecordFactory() throws ResourceException {
+        return null;
+    }
+
+    public ResourceAdapterMetaData getMetaData() throws ResourceException {
+        return null;
+    }
+
+    public void setReference(Reference reference) {
+        this.reference = reference;
+    }
+
+    public Reference getReference() throws NamingException {
+        return reference;
+    }
+
+    public String doSomethingElse() {
+        return "SomethingElse";
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockConnectionRequestInfo.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockConnectionRequestInfo.java
new file mode 100644
index 0000000..8f3e9b7
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockConnectionRequestInfo.java
@@ -0,0 +1,30 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.mock;
+
+import javax.resource.cci.ConnectionSpec;
+import javax.resource.spi.ConnectionRequestInfo;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class MockConnectionRequestInfo implements ConnectionRequestInfo, ConnectionSpec {
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockManagedConnection.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockManagedConnection.java
new file mode 100644
index 0000000..e743d66
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockManagedConnection.java
@@ -0,0 +1,199 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.mock;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import javax.resource.ResourceException;
+import javax.resource.spi.ConnectionEvent;
+import javax.resource.spi.ConnectionEventListener;
+import javax.resource.spi.ConnectionRequestInfo;
+import javax.resource.spi.LocalTransaction;
+import javax.resource.spi.ManagedConnection;
+import javax.resource.spi.ManagedConnectionMetaData;
+import javax.security.auth.Subject;
+import javax.transaction.xa.XAResource;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class MockManagedConnection implements ManagedConnection {
+
+    private final MockManagedConnectionFactory managedConnectionFactory;
+    private final MockXAResource mockXAResource;
+    private Subject subject;
+    private MockConnectionRequestInfo connectionRequestInfo;
+
+    private final Set connections = new HashSet();
+    private final List connectionEventListeners = Collections.synchronizedList(new ArrayList());
+
+    private boolean destroyed;
+    private PrintWriter logWriter;
+
+    public MockManagedConnection(MockManagedConnectionFactory managedConnectionFactory, Subject subject, MockConnectionRequestInfo connectionRequestInfo) {
+        this.managedConnectionFactory = managedConnectionFactory;
+        mockXAResource = new MockXAResource(this);
+        this.subject = subject;
+        this.connectionRequestInfo = connectionRequestInfo;
+    }
+
+    public Object getConnection(Subject subject, ConnectionRequestInfo connectionRequestInfo) throws ResourceException {
+        checkSecurityConsistency(subject, connectionRequestInfo);
+        MockConnection mockConnection = new MockConnection(this, subject, (MockConnectionRequestInfo) connectionRequestInfo);
+        connections.add(mockConnection);
+        return mockConnection;
+    }
+
+    private void checkSecurityConsistency(Subject subject, ConnectionRequestInfo connectionRequestInfo) {
+        if (!managedConnectionFactory.isReauthentication()) {
+            assert subject == null ? this.subject == null : subject.equals(this.subject);
+            assert connectionRequestInfo == null ? this.connectionRequestInfo == null : connectionRequestInfo.equals(this.connectionRequestInfo);
+        }
+    }
+
+    public void destroy() throws ResourceException {
+        destroyed = true;
+        cleanup();
+    }
+
+    public void cleanup() throws ResourceException {
+        for (Iterator iterator = new HashSet(connections).iterator(); iterator.hasNext();) {
+            MockConnection mockConnection = (MockConnection) iterator.next();
+            mockConnection.close();
+        }
+        assert connections.isEmpty();
+    }
+
+    public void associateConnection(Object connection) throws ResourceException {
+        assert connection != null;
+        assert connection.getClass() == MockConnection.class;
+        MockConnection mockConnection = (MockConnection) connection;
+        checkSecurityConsistency(mockConnection.getSubject(), mockConnection.getConnectionRequestInfo());
+        mockConnection.reassociate(this);
+        connections.add(mockConnection);
+    }
+
+    public void addConnectionEventListener(ConnectionEventListener listener) {
+        connectionEventListeners.add(listener);
+    }
+
+    public void removeConnectionEventListener(ConnectionEventListener listener) {
+        connectionEventListeners.remove(listener);
+    }
+
+    public XAResource getXAResource() throws ResourceException {
+        return mockXAResource;
+    }
+
+    public LocalTransaction getLocalTransaction() throws ResourceException {
+        return new MockSPILocalTransaction();
+    }
+
+    public ManagedConnectionMetaData getMetaData() throws ResourceException {
+        return null;
+    }
+
+    public void setLogWriter(PrintWriter logWriter) throws ResourceException {
+        this.logWriter = logWriter;
+    }
+
+    public PrintWriter getLogWriter() throws ResourceException {
+        return logWriter;
+    }
+
+    public Subject getSubject() {
+        return subject;
+    }
+
+    public MockConnectionRequestInfo getConnectionRequestInfo() {
+        return connectionRequestInfo;
+    }
+
+    public void removeHandle(MockConnection mockConnection) {
+        connections.remove(mockConnection);
+    }
+
+    public MockManagedConnectionFactory getManagedConnectionFactory() {
+        return managedConnectionFactory;
+    }
+
+    public Set getConnections() {
+        return connections;
+    }
+
+    public List getConnectionEventListeners() {
+        return connectionEventListeners;
+    }
+
+    public boolean isDestroyed() {
+        return destroyed;
+    }
+
+    public void closedEvent(MockConnection mockConnection) {
+        ConnectionEvent connectionEvent = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED);
+        connectionEvent.setConnectionHandle(mockConnection);
+        for (Iterator iterator = new ArrayList(connectionEventListeners).iterator(); iterator.hasNext();) {
+            ConnectionEventListener connectionEventListener = (ConnectionEventListener) iterator.next();
+            connectionEventListener.connectionClosed(connectionEvent);
+        }
+    }
+
+    public void errorEvent(MockConnection mockConnection) {
+        ConnectionEvent connectionEvent = new ConnectionEvent(this, ConnectionEvent.CONNECTION_ERROR_OCCURRED);
+        connectionEvent.setConnectionHandle(mockConnection);
+        for (Iterator iterator = new ArrayList(connectionEventListeners).iterator(); iterator.hasNext();) {
+            ConnectionEventListener connectionEventListener = (ConnectionEventListener) iterator.next();
+            connectionEventListener.connectionErrorOccurred(connectionEvent);
+        }
+    }
+
+    public void localTransactionStartedEvent(MockConnection mockConnection) {
+        ConnectionEvent connectionEvent = new ConnectionEvent(this, ConnectionEvent.LOCAL_TRANSACTION_STARTED);
+        connectionEvent.setConnectionHandle(mockConnection);
+        for (Iterator iterator = new ArrayList(connectionEventListeners).iterator(); iterator.hasNext();) {
+            ConnectionEventListener connectionEventListener = (ConnectionEventListener) iterator.next();
+            connectionEventListener.localTransactionStarted(connectionEvent);
+        }
+    }
+
+    public void localTransactionCommittedEvent(MockConnection mockConnection) {
+        ConnectionEvent connectionEvent = new ConnectionEvent(this, ConnectionEvent.LOCAL_TRANSACTION_COMMITTED);
+        connectionEvent.setConnectionHandle(mockConnection);
+        for (Iterator iterator = new ArrayList(connectionEventListeners).iterator(); iterator.hasNext();) {
+            ConnectionEventListener connectionEventListener = (ConnectionEventListener) iterator.next();
+            connectionEventListener.localTransactionCommitted(connectionEvent);
+        }
+    }
+
+    public void localTransactionRolledBackEvent(MockConnection mockConnection) {
+        ConnectionEvent connectionEvent = new ConnectionEvent(this, ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK);
+        connectionEvent.setConnectionHandle(mockConnection);
+        for (Iterator iterator = new ArrayList(connectionEventListeners).iterator(); iterator.hasNext();) {
+            ConnectionEventListener connectionEventListener = (ConnectionEventListener) iterator.next();
+            connectionEventListener.localTransactionRolledback(connectionEvent);
+        }
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockManagedConnectionFactory.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockManagedConnectionFactory.java
new file mode 100644
index 0000000..b32a008
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockManagedConnectionFactory.java
@@ -0,0 +1,153 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.mock;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Collections;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.ConnectionManager;
+import javax.resource.spi.ConnectionRequestInfo;
+import javax.resource.spi.ManagedConnection;
+import javax.resource.spi.ManagedConnectionFactory;
+import javax.resource.spi.ResourceAdapter;
+import javax.security.auth.Subject;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class MockManagedConnectionFactory implements ManagedConnectionFactory {
+
+    private MockResourceAdapter resourceAdapter;
+    private PrintWriter logWriter;
+
+    private final Set managedConnections = Collections.synchronizedSet(new HashSet());
+
+    private boolean reauthentication;
+
+    public String getOutboundStringProperty1() {
+        return outboundStringProperty1;
+    }
+
+    public void setOutboundStringProperty1(String outboundStringProperty1) {
+        this.outboundStringProperty1 = outboundStringProperty1;
+    }
+
+    public String getOutboundStringProperty2() {
+        return outboundStringProperty2;
+    }
+
+    public void setOutboundStringProperty2(String outboundStringProperty2) {
+        this.outboundStringProperty2 = outboundStringProperty2;
+    }
+
+    public String getOutboundStringProperty3() {
+        return outboundStringProperty3;
+    }
+
+    public void setOutboundStringProperty3(String outboundStringProperty3) {
+        this.outboundStringProperty3 = outboundStringProperty3;
+    }
+
+    public String getOutboundStringProperty4() {
+        return outboundStringProperty4;
+    }
+
+    public void setOutboundStringProperty4(String outboundStringProperty4) {
+        this.outboundStringProperty4 = outboundStringProperty4;
+    }
+
+    private String outboundStringProperty1;
+    private String outboundStringProperty2;
+    private String outboundStringProperty3;
+    private String outboundStringProperty4;
+
+    public void setResourceAdapter(ResourceAdapter resourceAdapter) throws ResourceException {
+        assert this.resourceAdapter == null: "Setting ResourceAdapter twice";
+        assert resourceAdapter != null: "trying to set resourceAdapter to null";
+        this.resourceAdapter = (MockResourceAdapter) resourceAdapter;
+    }
+
+    public ResourceAdapter getResourceAdapter() {
+        return resourceAdapter;
+    }
+
+    public Object createConnectionFactory(ConnectionManager connectionManager) throws ResourceException {
+        return new MockConnectionFactory(this, connectionManager);
+    }
+
+    public Object createConnectionFactory() throws ResourceException {
+        return null;
+    }
+
+    public ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo connectionRequestInfo) throws ResourceException {
+        MockManagedConnection managedConnection = new MockManagedConnection(this, subject, (MockConnectionRequestInfo) connectionRequestInfo);
+        managedConnections.add(managedConnection);
+        return managedConnection;
+    }
+
+    public ManagedConnection matchManagedConnections(Set connectionSet, Subject subject, ConnectionRequestInfo cxRequestInfo) throws ResourceException {
+        if (reauthentication) {
+            for (Iterator iterator = connectionSet.iterator(); iterator.hasNext();) {
+                ManagedConnection managedConnection = (ManagedConnection) iterator.next();
+                if (managedConnections.contains(managedConnection)) {
+                    return managedConnection;
+                }
+            }
+        } else {
+            for (Iterator iterator = connectionSet.iterator(); iterator.hasNext();) {
+                ManagedConnection managedConnection = (ManagedConnection) iterator.next();
+//                return managedConnection;
+                if (managedConnections.contains(managedConnection)) {
+                    MockManagedConnection mockManagedConnection = (MockManagedConnection) managedConnection;
+                    if ((subject == null ? mockManagedConnection.getSubject() == null : subject.equals(mockManagedConnection.getSubject())
+                            && (cxRequestInfo == null ? mockManagedConnection.getConnectionRequestInfo() == null : cxRequestInfo.equals(mockManagedConnection.getConnectionRequestInfo())))) {
+                        return mockManagedConnection;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    public void setLogWriter(PrintWriter logWriter) throws ResourceException {
+        this.logWriter = logWriter;
+    }
+
+    public PrintWriter getLogWriter() throws ResourceException {
+        return logWriter;
+    }
+
+    public boolean isReauthentication() {
+        return reauthentication;
+    }
+
+    public void setReauthentication(boolean reauthentication) {
+        this.reauthentication = reauthentication;
+    }
+
+    public Set getManagedConnections() {
+        return managedConnections;
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockResourceAdapter.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockResourceAdapter.java
new file mode 100644
index 0000000..c826690
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockResourceAdapter.java
@@ -0,0 +1,69 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.mock;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.ActivationSpec;
+import javax.resource.spi.BootstrapContext;
+import javax.resource.spi.ResourceAdapter;
+import javax.resource.spi.ResourceAdapterInternalException;
+import javax.resource.spi.endpoint.MessageEndpointFactory;
+import javax.transaction.xa.XAResource;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class MockResourceAdapter implements ResourceAdapter {
+
+
+    private BootstrapContext bootstrapContext;
+
+    private String raStringProperty;
+
+    public void start(BootstrapContext bootstrapContext) throws ResourceAdapterInternalException {
+        assert this.bootstrapContext == null : "Attempting to restart adapter without stoppping";
+        assert bootstrapContext != null: "Null bootstrap context";
+        this.bootstrapContext = bootstrapContext;
+    }
+
+    public void stop() {
+        bootstrapContext = null;
+    }
+
+    public void endpointActivation(MessageEndpointFactory endpointFactory, ActivationSpec spec) throws ResourceException {
+    }
+
+    public void endpointDeactivation(MessageEndpointFactory endpointFactory, ActivationSpec spec) {
+    }
+
+    public XAResource[] getXAResources(ActivationSpec[] specs) throws ResourceException {
+        return new XAResource[0];
+    }
+
+    public String getRAStringProperty() {
+        return raStringProperty;
+    }
+
+    public void setRAStringProperty(String raStringProperty) {
+        this.raStringProperty = raStringProperty;
+    }
+
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockSPILocalTransaction.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockSPILocalTransaction.java
new file mode 100644
index 0000000..e4c84d4
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockSPILocalTransaction.java
@@ -0,0 +1,80 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.mock;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.LocalTransaction;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class MockSPILocalTransaction implements LocalTransaction {
+
+
+    private boolean inTransaction;
+    private boolean begun;
+    private boolean committed;
+    private boolean rolledBack;
+
+    public MockSPILocalTransaction() {
+    }
+
+    public void begin() throws ResourceException {
+        assert !inTransaction;
+        inTransaction = true;
+        begun = true;
+    }
+
+    public void commit() throws ResourceException {
+        assert inTransaction;
+        inTransaction = false;
+        committed = true;
+    }
+
+    public void rollback() throws ResourceException {
+        assert inTransaction;
+        inTransaction = false;
+        rolledBack = true;
+    }
+
+    public void reset() {
+        inTransaction = false;
+        begun = false;
+        committed = false;
+        rolledBack = false;
+    }
+
+    public boolean isInTransaction() {
+        return inTransaction;
+    }
+
+    public boolean isBegun() {
+        return begun;
+    }
+
+    public boolean isCommitted() {
+        return committed;
+    }
+
+    public boolean isRolledBack() {
+        return rolledBack;
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockXAResource.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockXAResource.java
new file mode 100644
index 0000000..7686333
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/mock/MockXAResource.java
@@ -0,0 +1,160 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.mock;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class MockXAResource implements XAResource {
+
+    private final MockManagedConnection mockManagedConnection;
+    private int prepareResult = XAResource.XA_OK;
+    private Xid currentXid;
+    private int transactionTimeoutSeconds;
+    private final Set knownXids = new HashSet();
+    private final Set successfulXids = new HashSet();
+    private Xid prepared;
+    private Xid committed;
+    private Xid rolledback;
+
+    public MockXAResource(MockManagedConnection mockManagedConnection) {
+        this.mockManagedConnection = mockManagedConnection;
+    }
+
+    public void commit(Xid xid, boolean onePhase) throws XAException {
+        assert xid != null;
+        assert onePhase || prepared == xid;
+        committed = xid;
+    }
+
+    //TODO TMFAIL? TMENDRSCAN?
+    public void end(Xid xid, int flags) throws XAException {
+        assert xid != null;
+        assert knownXids.contains(xid);
+        assert flags == XAResource.TMSUSPEND || flags == XAResource.TMSUCCESS;
+        if (flags == XAResource.TMSUSPEND) {
+            assert currentXid == xid;
+            currentXid = null;
+        }
+        if (flags == XAResource.TMSUCCESS) {
+            successfulXids.add(xid);
+            if (xid.equals(currentXid)) {
+                currentXid = null;
+            }
+        }
+    }
+
+    public void forget(Xid xid) throws XAException {
+        //todo
+    }
+
+    public int getTransactionTimeout() throws XAException {
+        return transactionTimeoutSeconds;
+    }
+
+    public boolean isSameRM(XAResource xaResource) throws XAException {
+        if (!(xaResource instanceof MockXAResource)) {
+            return false;
+        }
+        MockXAResource other = (MockXAResource) xaResource;
+        return other.mockManagedConnection.getManagedConnectionFactory() == mockManagedConnection.getManagedConnectionFactory();
+    }
+
+    public int prepare(Xid xid) throws XAException {
+        assert xid != null;
+        prepared = xid;
+        return prepareResult;
+    }
+
+    public Xid[] recover(int flag) throws XAException {
+        //todo
+        return new Xid[0];
+    }
+
+    public void rollback(Xid xid) throws XAException {
+        assert xid != null;
+        rolledback = xid;
+    }
+
+    public boolean setTransactionTimeout(int seconds) throws XAException {
+        transactionTimeoutSeconds = seconds;
+        return true;
+    }
+
+    //TODO TMSTARTRSCAN?
+    public void start(Xid xid, int flags) throws XAException {
+        assert currentXid == null :"Expected no xid when start called";
+        assert xid != null: "Expected xid supplied to start";
+        assert flags == XAResource.TMNOFLAGS || flags == XAResource.TMJOIN || flags == XAResource.TMRESUME;
+        if (flags == XAResource.TMNOFLAGS || flags == XAResource.TMJOIN) {
+            assert !knownXids.contains(xid);
+            knownXids.add(xid);
+        }
+        if (flags == XAResource.TMRESUME) {
+            assert knownXids.contains(xid);
+        }
+        currentXid = xid;
+    }
+
+    public void setPrepareResult(int prepareResult) {
+        this.prepareResult = prepareResult;
+    }
+
+    public Xid getCurrentXid() {
+        return currentXid;
+    }
+
+    public Set getKnownXids() {
+        return knownXids;
+    }
+
+    public Set getSuccessfulXids() {
+        return successfulXids;
+    }
+
+    public Xid getPrepared() {
+        return prepared;
+    }
+
+    public Xid getCommitted() {
+        return committed;
+    }
+
+    public Xid getRolledback() {
+        return rolledback;
+    }
+
+    public void clear() {
+        currentXid = null;
+        prepared = null;
+        rolledback = null;
+        committed = null;
+        knownXids.clear();
+        successfulXids.clear();
+        prepareResult = XAResource.XA_OK;
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionInterceptorTestUtils.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionInterceptorTestUtils.java
new file mode 100644
index 0000000..c03acbf
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionInterceptorTestUtils.java
@@ -0,0 +1,186 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import java.io.PrintWriter;
+import java.security.Principal;
+import java.util.Set;
+import javax.resource.ResourceException;
+import javax.resource.spi.ConnectionEventListener;
+import javax.resource.spi.ConnectionRequestInfo;
+import javax.resource.spi.DissociatableManagedConnection;
+import javax.resource.spi.LocalTransaction;
+import javax.resource.spi.ManagedConnection;
+import javax.resource.spi.ManagedConnectionMetaData;
+import javax.security.auth.Subject;
+import javax.transaction.xa.XAResource;
+
+import junit.framework.TestCase;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class ConnectionInterceptorTestUtils extends TestCase implements ConnectionInterceptor {
+    protected Subject subject;
+    protected ConnectionInfo obtainedConnectionInfo;
+    protected ConnectionInfo returnedConnectionInfo;
+    protected ManagedConnection managedConnection;
+
+    protected void setUp() throws Exception {
+    }
+
+    protected void tearDown() throws Exception {
+        subject = null;
+        obtainedConnectionInfo = null;
+        returnedConnectionInfo = null;
+        managedConnection = null;
+    }
+
+    public void testNothing() throws Exception {
+    }
+
+    //ConnectorInterceptor implementation
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        ManagedConnectionInfo managedConnectionInfo = connectionInfo.getManagedConnectionInfo();
+        if (managedConnectionInfo.getManagedConnection() == null) {
+            managedConnectionInfo.setManagedConnection(managedConnection);
+        }
+        obtainedConnectionInfo = connectionInfo;
+    }
+
+    public void returnConnection(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
+        returnedConnectionInfo = connectionInfo;
+    }
+    
+    public void destroy() {
+        
+    }
+
+    protected void makeSubject(String principalName) {
+        subject = new Subject();
+        Set principals = subject.getPrincipals();
+        principals.add(new TestPrincipal(principalName));
+    }
+
+    protected ConnectionInfo makeConnectionInfo() {
+        ManagedConnectionInfo managedConnectionInfo = new ManagedConnectionInfo(null, null);
+        return new ConnectionInfo(managedConnectionInfo);
+    }
+
+    private static class TestPrincipal implements Principal {
+
+        private final String name;
+
+        public TestPrincipal(String name) {
+            this.name = name;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+    }
+
+    protected static class TestPlainManagedConnection implements ManagedConnection {
+        public Object getConnection(Subject subject, ConnectionRequestInfo cxRequestInfo) throws ResourceException {
+            return null;
+        }
+
+        public void destroy() throws ResourceException {
+        }
+
+        public void cleanup() throws ResourceException {
+        }
+
+        public void associateConnection(Object connection) throws ResourceException {
+        }
+
+        public void addConnectionEventListener(ConnectionEventListener listener) {
+        }
+
+        public void removeConnectionEventListener(ConnectionEventListener listener) {
+        }
+
+        public XAResource getXAResource() throws ResourceException {
+            return null;
+        }
+
+        public LocalTransaction getLocalTransaction() throws ResourceException {
+            return null;
+        }
+
+        public ManagedConnectionMetaData getMetaData() throws ResourceException {
+            return null;
+        }
+
+        public void setLogWriter(PrintWriter out) throws ResourceException {
+        }
+
+        public PrintWriter getLogWriter() throws ResourceException {
+            return null;
+        }
+
+    }
+
+    protected static class TestDissociatableManagedConnection implements ManagedConnection, DissociatableManagedConnection {
+        public void dissociateConnections() throws ResourceException {
+        }
+
+        public Object getConnection(Subject subject, ConnectionRequestInfo cxRequestInfo) throws ResourceException {
+            return null;
+        }
+
+        public void destroy() throws ResourceException {
+        }
+
+        public void cleanup() throws ResourceException {
+        }
+
+        public void associateConnection(Object connection) throws ResourceException {
+        }
+
+        public void addConnectionEventListener(ConnectionEventListener listener) {
+        }
+
+        public void removeConnectionEventListener(ConnectionEventListener listener) {
+        }
+
+        public XAResource getXAResource() throws ResourceException {
+            return null;
+        }
+
+        public LocalTransaction getLocalTransaction() throws ResourceException {
+            return null;
+        }
+
+        public ManagedConnectionMetaData getMetaData() throws ResourceException {
+            return null;
+        }
+
+        public void setLogWriter(PrintWriter out) throws ResourceException {
+        }
+
+        public PrintWriter getLogWriter() throws ResourceException {
+            return null;
+        }
+
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionManagerStressTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionManagerStressTest.java
new file mode 100644
index 0000000..8c3a6b6
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionManagerStressTest.java
@@ -0,0 +1,116 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import java.util.HashSet;
+
+import org.apache.geronimo.connector.outbound.connectiontracking.ConnectorInstanceContextImpl;
+
+/**
+ * ???
+ *
+ * @version $Rev$ $Date$
+ */
+public class ConnectionManagerStressTest extends ConnectionManagerTestUtils {
+
+    protected int repeatCount = 200;
+    protected int threadCount = 10;
+    private Object startBarrier = new Object();
+    private Object stopBarrier = new Object();
+    private int startedThreads = 0;
+    private int stoppedThreads = 0;
+    private long totalDuration = 0;
+    private int slowCount = 0;
+    private Object mutex = new Object();
+
+    private Exception e = null;
+
+    public void testNoTransactionCallOneThread() throws Throwable {
+        for (int i = 0; i < repeatCount; i++) {
+            defaultComponentInterceptor.invoke(connectorInstanceContext);
+        }
+    }
+
+    public void testNoTransactionCallMultiThread() throws Throwable {
+        startedThreads = 0;
+        stoppedThreads = 0;
+        for (int t = 0; t < threadCount; t++) {
+            new Thread() {
+                public void run() {
+                    long localStartTime = 0;
+                    int localSlowCount = 0;
+                    try {
+                        synchronized (startBarrier) {
+                            ++startedThreads;
+                            startBarrier.notifyAll();
+                            while (startedThreads < (threadCount + 1)) {
+                                startBarrier.wait();
+                            }
+                        }
+                        localStartTime = System.currentTimeMillis();
+                        for (int i = 0; i < repeatCount; i++) {
+                            try {
+                                long start = System.currentTimeMillis();
+                                defaultComponentInterceptor.invoke(new ConnectorInstanceContextImpl(new HashSet(), new HashSet()));
+                                long duration = System.currentTimeMillis() - start;
+                                if (duration > 100) {
+                                    localSlowCount++;
+                                    log.debug("got a cx: " + i + ", time: " + (duration));
+                                }
+                            } catch (Throwable throwable) {
+                                log.debug(throwable.getMessage(), throwable);
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.info(e.getMessage(), e);
+                        ConnectionManagerStressTest.this.e = e;
+                    } finally {
+                        synchronized (stopBarrier) {
+                            ++stoppedThreads;
+                            stopBarrier.notifyAll();
+                        }
+                        long localDuration = System.currentTimeMillis() - localStartTime;
+                        synchronized (mutex) {
+                            totalDuration += localDuration;
+                            slowCount += localSlowCount;
+                        }
+                    }
+                }
+            }.start();
+        }
+        // Wait for all the workers to be ready..
+        long startTime = 0;
+        synchronized (startBarrier) {
+            while (startedThreads < threadCount) startBarrier.wait();
+            ++startedThreads;
+            startBarrier.notifyAll();
+            startTime = System.currentTimeMillis();
+        }
+
+        // Wait for all the workers to finish.
+        synchronized (stopBarrier) {
+            while (stoppedThreads < threadCount) stopBarrier.wait();
+        }
+        long duration = System.currentTimeMillis() - startTime;
+        log.debug("no tx run, thread count: " + threadCount + ", connection count: " + repeatCount + ", duration: " + duration + ", total duration: " + totalDuration + ", ms per cx request: " + (totalDuration / (threadCount * repeatCount)) + ", slow cx request count: " + slowCount);
+        //return startTime;
+        if (e != null) {
+            throw e;
+        }
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionManagerTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionManagerTest.java
new file mode 100644
index 0000000..3e19711
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionManagerTest.java
@@ -0,0 +1,122 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import org.apache.geronimo.connector.mock.MockXAResource;
+import org.apache.geronimo.connector.mock.ConnectionExtension;
+import org.apache.geronimo.connector.outbound.connectiontracking.DefaultInterceptor;
+import org.apache.geronimo.connector.outbound.connectiontracking.ConnectorInstanceContext;
+import org.apache.geronimo.transaction.GeronimoUserTransaction;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class ConnectionManagerTest extends ConnectionManagerTestUtils {
+
+
+    public void testSingleTransactionCall() throws Throwable {
+        transactionManager.begin();
+        defaultComponentInterceptor.invoke(connectorInstanceContext);
+        MockXAResource mockXAResource = (MockXAResource) mockManagedConnection.getXAResource();
+        assertEquals("XAResource should know one xid", 1, mockXAResource.getKnownXids().size());
+        assertNull("Should not be committed", mockXAResource.getCommitted());
+        transactionManager.commit();
+        assertNotNull("Should be committed", mockXAResource.getCommitted());
+    }
+
+    public void testNoTransactionCall() throws Throwable {
+        defaultComponentInterceptor.invoke(connectorInstanceContext);
+        MockXAResource mockXAResource = (MockXAResource) mockManagedConnection.getXAResource();
+        assertEquals("XAResource should know 0 xid", 0, mockXAResource.getKnownXids().size());
+        assertNull("Should not be committed", mockXAResource.getCommitted());
+    }
+
+    public void testOneTransactionTwoCalls() throws Throwable {
+        transactionManager.begin();
+        defaultComponentInterceptor.invoke(connectorInstanceContext);
+        MockXAResource mockXAResource = (MockXAResource) mockManagedConnection.getXAResource();
+        assertEquals("XAResource should know one xid", 1, mockXAResource.getKnownXids().size());
+        assertNull("Should not be committed", mockXAResource.getCommitted());
+        defaultComponentInterceptor.invoke(connectorInstanceContext);
+        assertEquals("Expected same XAResource", mockXAResource, mockManagedConnection.getXAResource());
+        assertEquals("XAResource should know one xid", 1, mockXAResource.getKnownXids().size());
+        assertNull("Should not be committed", mockXAResource.getCommitted());
+        transactionManager.commit();
+        assertNotNull("Should be committed", mockXAResource.getCommitted());
+    }
+
+    public void testUserTransactionEnlistingExistingConnections() throws Throwable {
+        mockComponent = new DefaultInterceptor() {
+            public Object invoke(ConnectorInstanceContext newConnectorInstanceContext) throws Throwable {
+                ConnectionExtension mockConnection = (ConnectionExtension) connectionFactory.getConnection();
+                mockManagedConnection = mockConnection.getManagedConnection();
+                userTransaction.begin();
+                MockXAResource mockXAResource = (MockXAResource) mockManagedConnection.getXAResource();
+                assertEquals("XAResource should know one xid", 1, mockXAResource.getKnownXids().size());
+                assertNull("Should not be committed", mockXAResource.getCommitted());
+                userTransaction.commit();
+                assertNotNull("Should be committed", mockXAResource.getCommitted());
+                mockConnection.close();
+                return null;
+            }
+        };
+        userTransaction = new GeronimoUserTransaction(transactionManager);
+        defaultComponentInterceptor.invoke(connectorInstanceContext);
+        MockXAResource mockXAResource = (MockXAResource) mockManagedConnection.getXAResource();
+        assertEquals("XAResource should know 1 xid", 1, mockXAResource.getKnownXids().size());
+        assertNotNull("Should be committed", mockXAResource.getCommitted());
+        mockXAResource.clear();
+    }
+
+    public void testConnectionCloseReturnsCxAfterUserTransaction() throws Throwable {
+        for (int i = 0; i < maxSize + 1; i++) {
+            testUserTransactionEnlistingExistingConnections();
+        }
+    }
+
+    public void testUnshareableConnections() throws Throwable {
+        unshareableResources.add(name);
+        mockComponent = new DefaultInterceptor() {
+            public Object invoke(ConnectorInstanceContext newConnectorInstanceContext) throws Throwable {
+                ConnectionExtension mockConnection1 = (ConnectionExtension) connectionFactory.getConnection();
+                mockManagedConnection = mockConnection1.getManagedConnection();
+                ConnectionExtension mockConnection2 = (ConnectionExtension) connectionFactory.getConnection();
+                //the 2 cx are for the same RM, so tm will call commit only one one (the first)
+                //mockManagedConnection = mockConnection2.getManagedConnection();
+                assertNotNull("Expected non-null managedconnection 1", mockConnection1.getManagedConnection());
+                assertNotNull("Expected non-null managedconnection 2", mockConnection2.getManagedConnection());
+                assertTrue("Expected different managed connections for each unshared handle", mockConnection1.getManagedConnection() != mockConnection2.getManagedConnection());
+
+                mockConnection1.close();
+                mockConnection2.close();
+                return null;
+            }
+
+        };
+        transactionManager.begin();
+        defaultComponentInterceptor.invoke(connectorInstanceContext);
+        MockXAResource mockXAResource = (MockXAResource) mockManagedConnection.getXAResource();
+        assertEquals("XAResource should know one xid", 1, mockXAResource.getKnownXids().size());
+        assertNull("Should not be committed", mockXAResource.getCommitted());
+        transactionManager.commit();
+        assertNotNull("Should be committed", mockXAResource.getCommitted());
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionManagerTestUtils.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionManagerTestUtils.java
new file mode 100644
index 0000000..8442bae
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionManagerTestUtils.java
@@ -0,0 +1,135 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.transaction.UserTransaction;
+
+import org.apache.geronimo.connector.mock.ConnectionExtension;
+import org.apache.geronimo.connector.mock.MockConnectionFactory;
+import org.apache.geronimo.connector.mock.MockManagedConnection;
+import org.apache.geronimo.connector.mock.MockManagedConnectionFactory;
+import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PartitionedPool;
+import org.apache.geronimo.connector.outbound.connectionmanagerconfig.PoolingSupport;
+import org.apache.geronimo.connector.outbound.connectionmanagerconfig.TransactionSupport;
+import org.apache.geronimo.connector.outbound.connectionmanagerconfig.XATransactions;
+import org.apache.geronimo.connector.outbound.connectiontracking.ConnectionTrackingCoordinator;
+import org.apache.geronimo.connector.outbound.connectiontracking.ConnectorInstanceContext;
+import org.apache.geronimo.connector.outbound.connectiontracking.ConnectorInstanceContextImpl;
+import org.apache.geronimo.connector.outbound.connectiontracking.DefaultComponentInterceptor;
+import org.apache.geronimo.connector.outbound.connectiontracking.DefaultInterceptor;
+import org.apache.geronimo.connector.outbound.connectiontracking.GeronimoTransactionListener;
+import org.apache.geronimo.transaction.manager.RecoverableTransactionManager;
+import org.apache.geronimo.transaction.manager.TransactionManagerImpl;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import junit.framework.TestCase;
+
+/**
+ * ???
+ *
+ * @version $Rev$ $Date$
+ */
+public class ConnectionManagerTestUtils extends TestCase implements DefaultInterceptor {
+    protected static final Log log = LogFactory.getLog(ConnectionManagerTestUtils.class);
+    
+    protected boolean useTransactionCaching = true;
+    protected boolean useLocalTransactions = false;
+    protected boolean useThreadCaching = false;
+    protected boolean useTransactions = true;
+    protected int maxSize = 10;
+    protected int minSize = 0;
+    protected int blockingTimeout = 100;
+    protected int idleTimeoutMinutes = 15;
+    protected boolean useConnectionRequestInfo = false;
+    protected boolean useSubject = true;
+    private boolean matchOne = true;
+    private boolean matchAll = false;
+    private boolean selectOneNoMatch = false;
+    protected String name = "testCF";
+    //dependencies
+    protected ConnectionTrackingCoordinator connectionTrackingCoordinator;
+    protected RecoverableTransactionManager transactionManager;
+    protected AbstractConnectionManager connectionManagerDeployment;
+    protected MockConnectionFactory connectionFactory;
+    protected MockManagedConnectionFactory mockManagedConnectionFactory;
+    protected ConnectorInstanceContextImpl connectorInstanceContext;
+    protected DefaultComponentInterceptor defaultComponentInterceptor;
+    protected Set unshareableResources = new HashSet();
+    protected Set applicationManagedSecurityResources = new HashSet();
+    protected MockManagedConnection mockManagedConnection;
+    protected Subject subject;
+    protected UserTransaction userTransaction;
+    protected TransactionSupport transactionSupport = new XATransactions(useTransactionCaching, useThreadCaching);
+    protected PoolingSupport poolingSupport = new PartitionedPool(maxSize, minSize, blockingTimeout, idleTimeoutMinutes, matchOne, matchAll, selectOneNoMatch, useConnectionRequestInfo, useSubject);
+    protected boolean containerManagedSecurity = true;
+
+    protected DefaultInterceptor mockComponent = new DefaultInterceptor() {
+        public Object invoke(ConnectorInstanceContext newConnectorInstanceContext) throws Throwable {
+            ConnectionExtension mockConnection = (ConnectionExtension) connectionFactory.getConnection();
+            mockManagedConnection = mockConnection.getManagedConnection();
+            mockConnection.close();
+            return null;
+        }
+    };
+    private ClassLoader classLoader = this.getClass().getClassLoader();
+
+    protected void setUp() throws Exception {
+        TransactionManagerImpl transactionManager = new TransactionManagerImpl();
+        this.transactionManager = transactionManager;
+
+        connectionTrackingCoordinator = new ConnectionTrackingCoordinator();
+        transactionManager.addTransactionAssociationListener(new GeronimoTransactionListener(connectionTrackingCoordinator));
+
+        mockManagedConnectionFactory = new MockManagedConnectionFactory();
+        subject = new Subject();
+        SubjectSource subjectSource = new SubjectSource() {
+
+            public Subject getSubject() throws SecurityException {
+                return subject;
+            }
+        };
+        connectionManagerDeployment = new GenericConnectionManager(
+                transactionSupport,
+                poolingSupport,
+                containerManagedSecurity,
+                subjectSource, connectionTrackingCoordinator,
+                this.transactionManager,
+                name,
+                classLoader);
+        connectionFactory = (MockConnectionFactory) connectionManagerDeployment.createConnectionFactory(mockManagedConnectionFactory);
+        connectorInstanceContext = new ConnectorInstanceContextImpl(unshareableResources, applicationManagedSecurityResources);
+        defaultComponentInterceptor = new DefaultComponentInterceptor(this, connectionTrackingCoordinator);
+    }
+
+    protected void tearDown() throws Exception {
+        connectionTrackingCoordinator = null;
+        transactionManager = null;
+        mockManagedConnectionFactory = null;
+        connectionManagerDeployment = null;
+        connectionFactory = null;
+        connectorInstanceContext = null;
+    }
+
+    public Object invoke(ConnectorInstanceContext newConnectorInstanceContext) throws Throwable {
+        return mockComponent.invoke(newConnectorInstanceContext);
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionTrackingInterceptorTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionTrackingInterceptorTest.java
new file mode 100644
index 0000000..da255c0
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/ConnectionTrackingInterceptorTest.java
@@ -0,0 +1,161 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import javax.resource.ResourceException;
+
+import org.apache.geronimo.connector.outbound.connectiontracking.ConnectionTracker;
+
+/**
+ * TODO test unshareable resources.
+ * TODO test repeat calls with null/non-null Subject
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class ConnectionTrackingInterceptorTest extends ConnectionInterceptorTestUtils
+        implements ConnectionTracker {
+
+    private final static String key = "test-name";
+    private ConnectionTrackingInterceptor connectionTrackingInterceptor;
+
+
+    private ConnectionTrackingInterceptor obtainedConnectionTrackingInterceptor;
+    private ConnectionInfo obtainedTrackedConnectionInfo;
+
+    private ConnectionTrackingInterceptor releasedConnectionTrackingInterceptor;
+    private ConnectionInfo releasedTrackedConnectionInfo;
+
+    private Collection connectionInfos;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        connectionTrackingInterceptor = new ConnectionTrackingInterceptor(this, key, this);
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        connectionTrackingInterceptor = null;
+        managedConnection = null;
+        obtainedConnectionTrackingInterceptor = null;
+        obtainedTrackedConnectionInfo = null;
+        releasedConnectionTrackingInterceptor = null;
+        releasedTrackedConnectionInfo = null;
+    }
+
+    public void testConnectionRegistration() throws Exception {
+        ConnectionInfo connectionInfo = makeConnectionInfo();
+        connectionTrackingInterceptor.getConnection(connectionInfo);
+        assertTrue("Expected handleObtained call with our connectionTrackingInterceptor",
+                connectionTrackingInterceptor == obtainedConnectionTrackingInterceptor);
+        assertTrue("Expected handleObtained call with our connectionInfo",
+                connectionInfo == obtainedTrackedConnectionInfo);
+        //release connection handle
+        connectionTrackingInterceptor.returnConnection(connectionInfo, ConnectionReturnAction.RETURN_HANDLE);
+        assertTrue("Expected handleReleased call with our connectionTrackingInterceptor",
+                connectionTrackingInterceptor == releasedConnectionTrackingInterceptor);
+        assertTrue("Expected handleReleased call with our connectionInfo",
+                connectionInfo == releasedTrackedConnectionInfo);
+
+    }
+
+    private void getConnectionAndReenter() throws ResourceException {
+        ConnectionInfo connectionInfo = makeConnectionInfo();
+        connectionTrackingInterceptor.getConnection(connectionInfo);
+        //reset our test indicator
+        obtainedConnectionInfo = null;
+        connectionInfos = new HashSet();
+        connectionInfos.add(connectionInfo);
+        connectionTrackingInterceptor.enter(connectionInfos);
+    }
+
+    public void testEnterWithSameSubject() throws Exception {
+        makeSubject("foo");
+        getConnectionAndReenter();
+        //decision on re-association happens in subject interceptor
+        assertTrue("Expected connection asked for", obtainedConnectionInfo != null);
+        assertTrue("Expected no connection returned", returnedConnectionInfo == null);
+    }
+
+    public void testEnterWithChangedSubject() throws Exception {
+        testEnterWithSameSubject();
+        makeSubject("bar");
+        connectionTrackingInterceptor.enter(connectionInfos);
+        //expect re-association
+        assertTrue("Expected connection asked for", obtainedConnectionInfo != null);
+        //connection is returned by SubjectInterceptor
+        assertTrue("Expected no connection returned", returnedConnectionInfo == null);
+    }
+
+    public void testExitWithNonDissociatableConnection() throws Exception {
+        managedConnection = new TestPlainManagedConnection();
+        testEnterWithSameSubject();
+        connectionTrackingInterceptor.exit(connectionInfos);
+        assertTrue("Expected no connection returned", returnedConnectionInfo == null);
+        assertEquals("Expected one info in connectionInfos", connectionInfos.size(), 1);
+    }
+
+    public void testExitWithDissociatableConnection() throws Exception {
+        managedConnection = new TestDissociatableManagedConnection();
+        testEnterWithSameSubject();
+        assertEquals("Expected one info in connectionInfos", 1, connectionInfos.size());
+        connectionTrackingInterceptor.exit(connectionInfos);
+        assertTrue("Expected connection returned", returnedConnectionInfo != null);
+        assertEquals("Expected no infos in connectionInfos", 0, connectionInfos.size());
+    }
+
+    //ConnectionTracker interface
+    public void handleObtained(
+            ConnectionTrackingInterceptor connectionTrackingInterceptor,
+            ConnectionInfo connectionInfo, 
+            boolean reassociate) {
+        obtainedConnectionTrackingInterceptor = connectionTrackingInterceptor;
+        obtainedTrackedConnectionInfo = connectionInfo;
+    }
+
+    public void handleReleased(
+            ConnectionTrackingInterceptor connectionTrackingInterceptor,
+            ConnectionInfo connectionInfo, 
+            ConnectionReturnAction connectionReturnAction) {
+        releasedConnectionTrackingInterceptor = connectionTrackingInterceptor;
+        releasedTrackedConnectionInfo = connectionInfo;
+    }
+
+    public void setEnvironment(ConnectionInfo connectionInfo, String key) {
+        //unsharable = false, app security = false;
+    }
+
+    //ConnectionInterceptor interface
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        super.getConnection(connectionInfo);
+        ManagedConnectionInfo managedConnectionInfo = connectionInfo.getManagedConnectionInfo();
+        managedConnectionInfo.setConnectionEventListener(new GeronimoConnectionEventListener(null, managedConnectionInfo));
+        managedConnectionInfo.setSubject(subject);
+        if (managedConnectionInfo.getManagedConnection() == null) {
+            managedConnectionInfo.setManagedConnection(managedConnection);
+        }
+        if (connectionInfo.getConnectionHandle() == null) {
+            connectionInfo.setConnectionHandle(new Object());
+        }
+        managedConnectionInfo.addConnectionHandle(connectionInfo);
+    }
+
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/LocalXAResourceInsertionInterceptorTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/LocalXAResourceInsertionInterceptorTest.java
new file mode 100644
index 0000000..e8ee30a
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/LocalXAResourceInsertionInterceptorTest.java
@@ -0,0 +1,84 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+import javax.resource.spi.LocalTransaction;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class LocalXAResourceInsertionInterceptorTest extends ConnectionInterceptorTestUtils {
+
+    private LocalXAResourceInsertionInterceptor localXAResourceInsertionInterceptor;
+    private LocalTransaction localTransaction;
+    private String name = "LocalXAResource";
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        localXAResourceInsertionInterceptor = new LocalXAResourceInsertionInterceptor(this, name);
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        localXAResourceInsertionInterceptor = null;
+    }
+
+    public void testInsertLocalXAResource() throws Exception {
+        ConnectionInfo connectionInfo = makeConnectionInfo();
+        localXAResourceInsertionInterceptor.getConnection(connectionInfo);
+        LocalXAResource returnedLocalXAResource = (LocalXAResource) connectionInfo.getManagedConnectionInfo().getXAResource();
+        assertTrue("Expected the same LocalTransaction", localTransaction == returnedLocalXAResource.localTransaction);
+    }
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        super.getConnection(connectionInfo);
+        localTransaction = new TestLocalTransaction();
+        TestManagedConnection managedConnection = new TestManagedConnection(localTransaction);
+        ManagedConnectionInfo managedConnectionInfo = connectionInfo.getManagedConnectionInfo();
+        managedConnectionInfo.setManagedConnection(managedConnection);
+    }
+
+    private static class TestLocalTransaction implements LocalTransaction {
+        public void begin() throws ResourceException {
+        }
+
+        public void commit() throws ResourceException {
+        }
+
+        public void rollback() throws ResourceException {
+        }
+
+    }
+
+    private static class TestManagedConnection extends TestPlainManagedConnection {
+
+        private final LocalTransaction localTransaction;
+
+        public TestManagedConnection(LocalTransaction localTransaction) {
+            this.localTransaction = localTransaction;
+        }
+
+        public LocalTransaction getLocalTransaction() throws ResourceException {
+            return localTransaction;
+        }
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/PoolDequeTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/PoolDequeTest.java
new file mode 100644
index 0000000..df908ef
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/PoolDequeTest.java
@@ -0,0 +1,77 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import junit.framework.TestCase;
+
+
+/**
+ * PoolDequeTest.java
+ *
+ *
+ * Created: Fri Oct 10 12:00:47 2003
+ *
+ * @version 1.0
+ */
+public class PoolDequeTest extends TestCase {
+
+    private static final int MAX_SIZE = 10;
+
+    public PoolDequeTest(String name) {
+        super(name);
+    } // PoolDequeTest constructor
+
+
+    public void testFill() throws Exception {
+        SinglePoolConnectionInterceptor.PoolDeque pool = new SinglePoolConnectionInterceptor.PoolDeque(MAX_SIZE);
+        for (int i = 0; i < MAX_SIZE; i++) {
+            pool.add(new ManagedConnectionInfo(null, null));
+        }
+    }
+
+
+    public void testFillAndEmptyLast() throws Exception {
+        SinglePoolConnectionInterceptor.PoolDeque pool = new SinglePoolConnectionInterceptor.PoolDeque(MAX_SIZE);
+        ManagedConnectionInfo[] mcis = new ManagedConnectionInfo[MAX_SIZE];
+        for (int i = 0; i < MAX_SIZE; i++) {
+            mcis[i] = new ManagedConnectionInfo(null, null);
+            pool.add(mcis[i]);
+        }
+
+        for (int i = MAX_SIZE - 1; i >= 0; i--) {
+            assertTrue("Expected to get corresponding MCI from pool", mcis[i] == pool.peek(i));
+            assertTrue("Expected to get corresponding MCI from pool", mcis[i] == pool.removeLast());
+        }
+        assertTrue("Expected pool to be empty!", pool.isEmpty());
+    }
+
+    public void testRemove() throws Exception {
+        SinglePoolConnectionInterceptor.PoolDeque pool = new SinglePoolConnectionInterceptor.PoolDeque(MAX_SIZE);
+        ManagedConnectionInfo[] mcis = new ManagedConnectionInfo[MAX_SIZE];
+        for (int i = 0; i < MAX_SIZE; i++) {
+            mcis[i] = new ManagedConnectionInfo(null, null);
+            pool.add(mcis[i]);
+        }
+
+        for (int i = 0; i < MAX_SIZE; i++) {
+            assertTrue("Expected to find MCI in pool", pool.remove(mcis[i]));
+        }
+        assertTrue("Expected pool to be empty!", pool.isEmpty());
+    }
+
+} // PoolDequeTest
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/PoolResizeTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/PoolResizeTest.java
new file mode 100644
index 0000000..a62483d
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/PoolResizeTest.java
@@ -0,0 +1,72 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector.outbound;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class PoolResizeTest extends TestCase {
+
+    private final int oldCheckedOut = 20;
+    private final int oldConnectionCount = 40;
+    private final int oldPermitsAvailable = oldConnectionCount - oldCheckedOut;
+    private final int oldMaxSize = 60;
+
+    public void testOne() throws Exception {
+        int oldMinSize = 5;
+        int newMaxSize = 10;
+        AbstractSinglePoolConnectionInterceptor.ResizeInfo resizeInfo = new AbstractSinglePoolConnectionInterceptor.ResizeInfo(oldMinSize, oldPermitsAvailable, oldConnectionCount, newMaxSize);
+        assertEquals("wrong shrinkLater", 10, resizeInfo.getShrinkLater());
+        assertEquals("wrong shrinkNow", 20, resizeInfo.getShrinkNow());
+        assertEquals("wrong newMinSize", 5, resizeInfo.getNewMinSize());
+        assertEquals("wrong transferCheckedOut", 10, resizeInfo.getTransferCheckedOut());
+    }
+
+    public void testTwo() throws Exception {
+        int oldMinSize = 5;
+        int newMaxSize = 30;
+        AbstractSinglePoolConnectionInterceptor.ResizeInfo resizeInfo = new AbstractSinglePoolConnectionInterceptor.ResizeInfo(oldMinSize, oldPermitsAvailable, oldConnectionCount, newMaxSize);
+        assertEquals("wrong shrinkLater", 0, resizeInfo.getShrinkLater());
+        assertEquals("wrong shrinkNow", 10, resizeInfo.getShrinkNow());
+        assertEquals("wrong newMinSize", 5, resizeInfo.getNewMinSize());
+        assertEquals("wrong transferCheckedOut", 20, resizeInfo.getTransferCheckedOut());
+    }
+
+    public void testThree() throws Exception {
+        int oldMinSize = 5;
+        int newMaxSize = 50;
+        AbstractSinglePoolConnectionInterceptor.ResizeInfo resizeInfo = new AbstractSinglePoolConnectionInterceptor.ResizeInfo(oldMinSize, oldPermitsAvailable, oldConnectionCount, newMaxSize);
+        assertEquals("wrong shrinkLater", 00, resizeInfo.getShrinkLater());
+        assertEquals("wrong shrinkNow", 0, resizeInfo.getShrinkNow());
+        assertEquals("wrong newMinSize", 5, resizeInfo.getNewMinSize());
+        assertEquals("wrong transferCheckedOut", 20, resizeInfo.getTransferCheckedOut());
+    }
+
+    public void testFour() throws Exception {
+        int oldMinSize = 5;
+        int newMaxSize = 70;
+        AbstractSinglePoolConnectionInterceptor.ResizeInfo resizeInfo = new AbstractSinglePoolConnectionInterceptor.ResizeInfo(oldMinSize, oldPermitsAvailable, oldConnectionCount, newMaxSize);
+        assertEquals("wrong shrinkLater", 0, resizeInfo.getShrinkLater());
+        assertEquals("wrong shrinkNow", 0, resizeInfo.getShrinkNow());
+        assertEquals("wrong newMinSize", 5, resizeInfo.getNewMinSize());
+        assertEquals("wrong transferCheckedOut", 20, resizeInfo.getTransferCheckedOut());
+    }
+
+
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/SubjectInterceptorTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/SubjectInterceptorTest.java
new file mode 100644
index 0000000..3fd5291
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/SubjectInterceptorTest.java
@@ -0,0 +1,121 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.security.auth.Subject;
+import javax.resource.ResourceException;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class SubjectInterceptorTest extends ConnectionInterceptorTestUtils {
+
+    private SubjectInterceptor subjectInterceptor;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        SubjectSource subjectSource = new SubjectSource() {
+            public Subject getSubject() throws SecurityException {
+                return subject;
+            }
+        };
+
+        subjectInterceptor = new SubjectInterceptor(this, subjectSource);
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        subjectInterceptor = null;
+    }
+
+    public void testGetConnection() throws Exception {
+        subject = new Subject();
+        ConnectionInfo connectionInfo = makeConnectionInfo();
+        ManagedConnectionInfo managedConnectionInfo = connectionInfo.getManagedConnectionInfo();
+        subjectInterceptor.getConnection(connectionInfo);
+        assertTrue("Expected call to next with same connectionInfo", connectionInfo == obtainedConnectionInfo);
+        assertTrue("Expected the same managedConnectionInfo", managedConnectionInfo == connectionInfo.getManagedConnectionInfo());
+        assertTrue("Expected supplied subject to be inserted", subject == managedConnectionInfo.getSubject());
+    }
+
+    public void testReturnConnection() throws Exception {
+        ConnectionInfo connectionInfo = makeConnectionInfo();
+        subjectInterceptor.returnConnection(connectionInfo, ConnectionReturnAction.RETURN_HANDLE);
+        assertTrue("Expected call to next with same connectionInfo", connectionInfo == returnedConnectionInfo);
+    }
+
+    public void testEnterWithSameSubject() throws Exception {
+        makeSubject("foo");
+        ConnectionInfo connectionInfo = makeConnectionInfo();
+        managedConnection = new TestPlainManagedConnection();
+        subjectInterceptor.getConnection(connectionInfo);
+        //reset our test indicator
+        obtainedConnectionInfo = null;
+        subjectInterceptor.getConnection(connectionInfo);
+        assertTrue("Expected connection asked for", obtainedConnectionInfo == connectionInfo);
+        assertTrue("Expected no connection returned", returnedConnectionInfo == null);
+    }
+
+    public void testEnterWithChangedSubject() throws Exception {
+        makeSubject("foo");
+        ConnectionInfo connectionInfo = makeConnectionInfo();
+        managedConnection = new TestPlainManagedConnection();
+        subjectInterceptor.getConnection(connectionInfo);
+        //reset our test indicator
+        obtainedConnectionInfo = null;
+        makeSubject("bar");
+        subjectInterceptor.getConnection(connectionInfo);
+        //expect re-association
+        assertTrue("Expected connection asked for", obtainedConnectionInfo != null);
+        //connection is returned by SubjectInterceptor
+        assertTrue("Expected connection returned", returnedConnectionInfo != null);
+    }
+
+    public void testApplicationManagedSecurity() throws Exception {
+        makeSubject("foo");
+        ConnectionInfo connectionInfo = makeConnectionInfo();
+        connectionInfo.setApplicationManagedSecurity(true);
+        ManagedConnectionInfo managedConnectionInfo = connectionInfo.getManagedConnectionInfo();
+        managedConnection = new TestPlainManagedConnection();
+        subjectInterceptor.getConnection(connectionInfo);
+        //expect no subject set on mci
+        assertTrue("Expected call to next with same connectionInfo", connectionInfo == obtainedConnectionInfo);
+        assertTrue("Expected the same managedConnectionInfo", managedConnectionInfo == connectionInfo.getManagedConnectionInfo());
+        assertTrue("Expected no subject to be inserted", null == managedConnectionInfo.getSubject());
+    }
+
+    public void testUnshareablePreventsReAssociation() throws Exception {
+        makeSubject("foo");
+        ConnectionInfo connectionInfo = makeConnectionInfo();
+        connectionInfo.setUnshareable(true);
+        managedConnection = new TestPlainManagedConnection();
+        subjectInterceptor.getConnection(connectionInfo);
+        //reset our test indicator
+        obtainedConnectionInfo = null;
+        makeSubject("bar");
+        try {
+            subjectInterceptor.getConnection(connectionInfo);
+            fail("Reassociating should fail on an unshareable connection");
+        } catch (ResourceException e) {
+        }
+    }
+
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/TransactionCachingInterceptorTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/TransactionCachingInterceptorTest.java
new file mode 100644
index 0000000..1782091
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/TransactionCachingInterceptorTest.java
@@ -0,0 +1,185 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.Status;
+
+import org.apache.geronimo.connector.ConnectorTransactionContext;
+import org.apache.geronimo.transaction.manager.TransactionManagerImpl;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class TransactionCachingInterceptorTest extends ConnectionInterceptorTestUtils {
+    private TransactionManager transactionManager;
+    private TransactionCachingInterceptor transactionCachingInterceptor;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        transactionManager = new TransactionManagerImpl();
+        transactionCachingInterceptor = new TransactionCachingInterceptor(this, transactionManager);
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        transactionManager = null;
+        transactionCachingInterceptor = null;
+    }
+
+    public void testGetConnectionInTransaction() throws Exception {
+        transactionManager.begin();
+        ConnectionInfo connectionInfo1 = makeConnectionInfo();
+        transactionCachingInterceptor.getConnection(connectionInfo1);
+        assertTrue("Expected to get an initial connection", obtainedConnectionInfo != null);
+        assertTrue("Expected nothing returned yet", returnedConnectionInfo == null);
+        assertTrue("Expected the same ManagedConnectionInfo in the TransactionContext as was returned",
+                connectionInfo1.getManagedConnectionInfo()
+                == getSharedManagedConnectionInfo(transactionManager.getTransaction()));
+        obtainedConnectionInfo = null;
+        ConnectionInfo connectionInfo2 = new ConnectionInfo();
+        transactionCachingInterceptor.getConnection(connectionInfo2);
+        assertTrue("Expected to not get a second connection", obtainedConnectionInfo == null);
+        assertTrue("Expected nothing returned yet", returnedConnectionInfo == null);
+        assertTrue("Expected the same ManagedConnectionInfo in both ConnectionInfos",
+                connectionInfo1.getManagedConnectionInfo() == connectionInfo2.getManagedConnectionInfo());
+        assertTrue("Expected the same ManagedConnectionInfo in the TransactionContext as was returned",
+                connectionInfo1.getManagedConnectionInfo() == getSharedManagedConnectionInfo(transactionManager.getTransaction()));
+        //commit, see if connection returned.
+        //we didn't create any handles, so the "ManagedConnection" should be returned.
+        assertTrue("Expected TransactionContext to report active", TxUtil.isTransactionActive(transactionManager));
+        transactionManager.commit();
+        assertTrue("Expected connection to be returned", returnedConnectionInfo != null);
+        assertTrue("Expected TransactionContext to report inactive", !TxUtil.isTransactionActive(transactionManager));
+
+    }
+
+    public void testGetUnshareableConnectionsInTransaction() throws Exception {
+        transactionManager.begin();
+        ConnectionInfo connectionInfo1 = makeConnectionInfo();
+        connectionInfo1.setUnshareable(true);
+        transactionCachingInterceptor.getConnection(connectionInfo1);
+        assertTrue("Expected to get an initial connection", obtainedConnectionInfo != null);
+        assertTrue("Expected nothing returned yet", returnedConnectionInfo == null);
+
+        //2nd is shared, modelling a call into another ejb
+        obtainedConnectionInfo = null;
+        ConnectionInfo connectionInfo2 = makeConnectionInfo();
+        transactionCachingInterceptor.getConnection(connectionInfo2);
+        assertTrue("Expected to get a second connection", obtainedConnectionInfo != null);
+        assertTrue("Expected nothing returned yet", returnedConnectionInfo == null);
+        assertTrue("Expected the same ManagedConnectionInfo in both ConnectionInfos",
+                connectionInfo1.getManagedConnectionInfo() != connectionInfo2.getManagedConnectionInfo());
+
+        //3rd is unshared, modelling a call into a third ejb
+        obtainedConnectionInfo = null;
+        ConnectionInfo connectionInfo3 = makeConnectionInfo();
+        connectionInfo3.setUnshareable(true);
+        transactionCachingInterceptor.getConnection(connectionInfo3);
+        assertTrue("Expected to get a third connection", obtainedConnectionInfo != null);
+        assertTrue("Expected nothing returned yet", returnedConnectionInfo == null);
+        assertTrue("Expected different ManagedConnectionInfo in both unshared ConnectionInfos",
+                connectionInfo1.getManagedConnectionInfo() != connectionInfo3.getManagedConnectionInfo());
+
+        //commit, see if connection returned.
+        //we didn't create any handles, so the "ManagedConnection" should be returned.
+        assertTrue("Expected TransactionContext to report active", transactionManager.getStatus() == Status.STATUS_ACTIVE);
+        transactionManager.commit();
+        assertTrue("Expected connection to be returned", returnedConnectionInfo != null);
+    }
+
+    private ManagedConnectionInfo getSharedManagedConnectionInfo(Transaction transaction) {
+        if (transaction == null) return null;
+        return ConnectorTransactionContext.get(transaction, transactionCachingInterceptor).getShared();
+    }
+
+    public void testGetConnectionOutsideTransaction() throws Exception {
+        ConnectionInfo connectionInfo1 = makeConnectionInfo();
+        transactionCachingInterceptor.getConnection(connectionInfo1);
+        assertTrue("Expected to get an initial connection", obtainedConnectionInfo != null);
+        assertTrue("Expected nothing returned yet", returnedConnectionInfo == null);
+        obtainedConnectionInfo = null;
+        ConnectionInfo connectionInfo2 = makeConnectionInfo();
+        transactionCachingInterceptor.getConnection(connectionInfo2);
+        assertTrue("Expected to get a second connection", obtainedConnectionInfo != null);
+        assertTrue("Expected nothing returned yet", returnedConnectionInfo == null);
+        assertTrue("Expected different ManagedConnectionInfo in both ConnectionInfos",
+                connectionInfo1.getManagedConnectionInfo() != connectionInfo2.getManagedConnectionInfo());
+        //we didn't create any handles, so the "ManagedConnection" should be returned.
+        transactionCachingInterceptor.returnConnection(connectionInfo1, ConnectionReturnAction.RETURN_HANDLE);
+        assertTrue("Expected connection to be returned", returnedConnectionInfo != null);
+        returnedConnectionInfo = null;
+        transactionCachingInterceptor.returnConnection(connectionInfo2, ConnectionReturnAction.RETURN_HANDLE);
+        assertTrue("Expected connection to be returned", returnedConnectionInfo != null);
+    }
+
+    public void testTransactionIndependence() throws Exception {
+        transactionManager.begin();
+        ConnectionInfo connectionInfo1 = makeConnectionInfo();
+        transactionCachingInterceptor.getConnection(connectionInfo1);
+        obtainedConnectionInfo = null;
+
+        //start a second transaction
+        Transaction suspendedTransaction = transactionManager.suspend();
+        transactionManager.begin();
+        ConnectionInfo connectionInfo2 = makeConnectionInfo();
+        transactionCachingInterceptor.getConnection(connectionInfo2);
+        assertTrue("Expected to get a second connection", obtainedConnectionInfo != null);
+        assertTrue("Expected nothing returned yet", returnedConnectionInfo == null);
+        assertTrue("Expected different ManagedConnectionInfo in each ConnectionInfos",
+                connectionInfo1.getManagedConnectionInfo() != connectionInfo2.getManagedConnectionInfo());
+        assertTrue("Expected the same ManagedConnectionInfo in the TransactionContext as was returned",
+                connectionInfo2.getManagedConnectionInfo() == getSharedManagedConnectionInfo(transactionManager.getTransaction()));
+        //commit 2nd transaction, see if connection returned.
+        //we didn't create any handles, so the "ManagedConnection" should be returned.
+        assertTrue("Expected TransactionContext to report active", transactionManager.getStatus() == Status.STATUS_ACTIVE);
+        transactionManager.commit();
+        assertTrue("Expected connection to be returned", returnedConnectionInfo != null);
+        assertTrue("Expected TransactionContext to report inactive", transactionManager.getStatus() == Status.STATUS_NO_TRANSACTION);
+        returnedConnectionInfo = null;
+        //resume first transaction
+        transactionManager.resume(suspendedTransaction);
+        transactionManager.commit();
+        assertTrue("Expected connection to be returned", returnedConnectionInfo != null);
+        assertTrue("Expected TransactionContext to report inactive", transactionManager.getStatus() == Status.STATUS_NO_TRANSACTION);
+    }
+
+//interface implementations
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        super.getConnection(connectionInfo);
+        ManagedConnectionInfo managedConnectionInfo = connectionInfo.getManagedConnectionInfo();
+        managedConnectionInfo.setConnectionEventListener(new GeronimoConnectionEventListener(null, managedConnectionInfo));
+    }
+
+
+    public void handleObtained(
+            ConnectionTrackingInterceptor connectionTrackingInterceptor,
+            ConnectionInfo connectionInfo) {
+    }
+
+    public void handleReleased(
+            ConnectionTrackingInterceptor connectionTrackingInterceptor,
+            ConnectionInfo connectionInfo) {
+    }
+
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/TransactionEnlistingInterceptorTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/TransactionEnlistingInterceptorTest.java
new file mode 100644
index 0000000..8da1a07
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/TransactionEnlistingInterceptorTest.java
@@ -0,0 +1,150 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import javax.transaction.TransactionManager;
+
+import org.apache.geronimo.transaction.manager.NamedXAResource;
+import org.apache.geronimo.transaction.manager.TransactionManagerImpl;
+import org.apache.geronimo.transaction.manager.XidFactoryImpl;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class TransactionEnlistingInterceptorTest extends ConnectionInterceptorTestUtils implements NamedXAResource {
+    private TransactionEnlistingInterceptor transactionEnlistingInterceptor;
+    private boolean started;
+    private boolean ended;
+    private boolean returned;
+    private boolean committed;
+    private TransactionManager transactionManager;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        transactionManager = new TransactionManagerImpl();
+        transactionEnlistingInterceptor = new TransactionEnlistingInterceptor(this, this.transactionManager);
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        transactionEnlistingInterceptor = null;
+        started = false;
+        ended = false;
+        returned = false;
+        committed = false;
+    }
+
+    public void testNoTransaction() throws Exception {
+        ConnectionInfo connectionInfo = makeConnectionInfo();
+        transactionEnlistingInterceptor.getConnection(connectionInfo);
+        assertTrue("Expected not started", !started);
+        assertTrue("Expected not ended", !ended);
+        transactionEnlistingInterceptor.returnConnection(connectionInfo, ConnectionReturnAction.RETURN_HANDLE);
+        assertTrue("Expected returned", returned);
+        assertTrue("Expected not committed", !committed);
+    }
+
+    public void testTransactionShareableConnection() throws Exception {
+        transactionManager.begin();
+        ConnectionInfo connectionInfo = makeConnectionInfo();
+        transactionEnlistingInterceptor.getConnection(connectionInfo);
+        assertTrue("Expected started", started);
+        assertTrue("Expected not ended", !ended);
+        started = false;
+        transactionEnlistingInterceptor.returnConnection(connectionInfo, ConnectionReturnAction.RETURN_HANDLE);
+        assertTrue("Expected not started", !started);
+        assertTrue("Expected ended", ended);
+        assertTrue("Expected returned", returned);
+        transactionManager.commit();
+        assertTrue("Expected committed", committed);
+    }
+
+    public void testTransactionUnshareableConnection() throws Exception {
+        transactionManager.begin();
+        ConnectionInfo connectionInfo = makeConnectionInfo();
+        connectionInfo.setUnshareable(true);
+        transactionEnlistingInterceptor.getConnection(connectionInfo);
+        assertTrue("Expected started", started);
+        assertTrue("Expected not ended", !ended);
+        started = false;
+        transactionEnlistingInterceptor.returnConnection(connectionInfo, ConnectionReturnAction.RETURN_HANDLE);
+        assertTrue("Expected not started", !started);
+        assertTrue("Expected ended", ended);
+        assertTrue("Expected returned", returned);
+        transactionManager.commit();
+        assertTrue("Expected committed", committed);
+    }
+
+    //ConnectionInterceptor
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+        ManagedConnectionInfo managedConnectionInfo = connectionInfo.getManagedConnectionInfo();
+        managedConnectionInfo.setXAResource(this);
+    }
+
+    public void returnConnection(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
+        returned = true;
+    }
+
+    //XAResource
+    public void commit(Xid xid, boolean onePhase) throws XAException {
+        committed = true;
+    }
+
+    public void end(Xid xid, int flags) throws XAException {
+        ended = true;
+    }
+
+    public void forget(Xid xid) throws XAException {
+    }
+
+    public int getTransactionTimeout() throws XAException {
+        return 0;
+    }
+
+    public boolean isSameRM(XAResource xaResource) throws XAException {
+        return false;
+    }
+
+    public int prepare(Xid xid) throws XAException {
+        return 0;
+    }
+
+    public Xid[] recover(int flag) throws XAException {
+        return new Xid[0];
+    }
+
+    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 {
+        started = true;
+    }
+
+
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/XAResourceInsertionInterceptorTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/XAResourceInsertionInterceptorTest.java
new file mode 100644
index 0000000..8e795ed
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/XAResourceInsertionInterceptorTest.java
@@ -0,0 +1,113 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound;
+
+import javax.resource.ResourceException;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class XAResourceInsertionInterceptorTest extends ConnectionInterceptorTestUtils {
+
+    private XAResourceInsertionInterceptor xaResourceInsertionInterceptor;
+    private XAResource xaResource;
+    private String name = "XAResource";
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        xaResourceInsertionInterceptor = new XAResourceInsertionInterceptor(this, name);
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        xaResourceInsertionInterceptor = null;
+    }
+
+    public void testInsertXAResource() throws Exception {
+        ConnectionInfo connectionInfo = makeConnectionInfo();
+        xaResource = new TestXAResource();
+        managedConnection = new TestManagedConnection(xaResource);
+        xaResourceInsertionInterceptor.getConnection(connectionInfo);
+        xaResource.setTransactionTimeout(200);
+        //xaresource is wrapped, so we do something to ours to make it distinguishable.
+        assertEquals("Expected the same XAResource", 200, connectionInfo.getManagedConnectionInfo().getXAResource().getTransactionTimeout());
+    }
+
+
+    private static class TestXAResource implements XAResource {
+        private int txTimeout;
+
+        public void commit(Xid xid, boolean onePhase) throws XAException {
+        }
+
+        public void end(Xid xid, int flags) throws XAException {
+        }
+
+        public void forget(Xid xid) throws XAException {
+        }
+
+        public int getTransactionTimeout() throws XAException {
+            return txTimeout;
+        }
+
+        public boolean isSameRM(XAResource xaResource) throws XAException {
+            return false;
+        }
+
+        public int prepare(Xid xid) throws XAException {
+            return 0;
+        }
+
+        public Xid[] recover(int flag) throws XAException {
+            return new Xid[0];
+        }
+
+        public void rollback(Xid xid) throws XAException {
+        }
+
+        public boolean setTransactionTimeout(int seconds) throws XAException {
+            this.txTimeout = seconds;
+            return false;
+        }
+
+        public void start(Xid xid, int flags) throws XAException {
+        }
+
+    }
+
+    private static class TestManagedConnection extends TestPlainManagedConnection {
+
+        private final XAResource xaResource;
+
+        public TestManagedConnection(XAResource xaResource) {
+            this.xaResource = xaResource;
+        }
+
+        public XAResource getXAResource() throws ResourceException {
+            return xaResource;
+        }
+
+
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectionTrackingCoordinatorProxyTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectionTrackingCoordinatorProxyTest.java
new file mode 100644
index 0000000..e8ea77f
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectionTrackingCoordinatorProxyTest.java
@@ -0,0 +1,350 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.connector.outbound.connectiontracking;
+
+import junit.framework.TestCase;
+import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
+import org.apache.geronimo.connector.outbound.ConnectionTrackingInterceptor;
+import org.apache.geronimo.connector.outbound.ConnectionInfo;
+import org.apache.geronimo.connector.outbound.ConnectionReturnAction;
+import org.apache.geronimo.connector.outbound.ManagedConnectionInfo;
+import org.apache.geronimo.connector.outbound.GeronimoConnectionEventListener;
+
+import javax.security.auth.Subject;
+import javax.resource.ResourceException;
+import javax.resource.spi.ManagedConnection;
+import javax.resource.spi.ConnectionRequestInfo;
+import javax.resource.spi.ConnectionEventListener;
+import javax.resource.spi.LocalTransaction;
+import javax.resource.spi.ManagedConnectionMetaData;
+import javax.transaction.xa.XAResource;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Map;
+import java.lang.reflect.Proxy;
+import java.io.PrintWriter;
+
+/**
+ * @version $Rev: 476049 $ $Date: 2006-11-16 20:35:17 -0800 (Thu, 16 Nov 2006) $
+ */
+public class ConnectionTrackingCoordinatorProxyTest extends TestCase implements ConnectionInterceptor {
+    private static final String name1 = "foo";
+    private ConnectionTrackingCoordinator connectionTrackingCoordinator;
+    private ConnectionTrackingInterceptor key1;
+    private Subject subject = null;
+    private Set unshareableResources;
+    private Set applicationManagedSecurityResources;
+    private ManagedConnectionInfo mci;
+    private ConnectionImpl connection;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        connectionTrackingCoordinator = new ConnectionTrackingCoordinator(true);
+        key1 = new ConnectionTrackingInterceptor(this, name1, connectionTrackingCoordinator);
+        unshareableResources = new HashSet();
+        applicationManagedSecurityResources = new HashSet();
+
+        mci = new ManagedConnectionInfo(null, null);
+        mci.setManagedConnection(new MockManagedConnection());
+        mci.setConnectionEventListener(new GeronimoConnectionEventListener(this, mci));
+        connection = new ConnectionImpl("ConnectionString");
+    }
+
+    protected void tearDown() throws Exception {
+        connectionTrackingCoordinator = null;
+        key1 = null;
+        super.tearDown();
+    }
+
+    public void testSimpleComponentContextLifecyle() throws Exception {
+        // enter component context
+        ConnectorInstanceContextImpl componentContext = new ConnectorInstanceContextImpl(unshareableResources, applicationManagedSecurityResources);
+        ConnectorInstanceContext oldConnectorInstanceContext = connectionTrackingCoordinator.enter(componentContext);
+        assertNull("Expected old instance context to be null", oldConnectorInstanceContext);
+
+        // simulate create connection
+        ConnectionInfo connectionInfo = createConnectionInfo();
+        connectionTrackingCoordinator.handleObtained(key1, connectionInfo, false);
+
+        // connection should be in component instance context
+        Map connectionManagerMap = componentContext.getConnectionManagerMap();
+        Set infos = (Set) connectionManagerMap.get(key1);
+        assertNotNull("Expected one connections for key1", infos);
+        assertEquals("Expected one connection for key1", 1, infos.size());
+        assertTrue("Expected to get supplied ConnectionInfo from infos", connectionInfo == infos.iterator().next());
+
+        // verify handle
+        Object handle = connectionInfo.getConnectionHandle();
+        assertNotNull("Expected a handle from ConnectionInfo", handle);
+        assertTrue("Expected handle to be an instance of ConnectionImpl", handle instanceof ConnectionImpl);
+        ConnectionImpl connection = (ConnectionImpl) handle;
+        assertEquals("connection.getString()", "ConnectionString", connection.getString());
+
+        // verify proxy
+        Object proxy = connectionInfo.getConnectionProxy();
+        assertNotNull("Expected a proxy from ConnectionInfo", proxy);
+        assertTrue("Expected proxy to be an instance of Connection", proxy instanceof Connection);
+        Connection connectionProxy = (Connection) proxy;
+        assertEquals("connection.getString()", "ConnectionString", connectionProxy.getString());
+        assertSame("Expected connection.getUnmanaged() to be connectionImpl", connection, connectionProxy.getUnmanaged());
+        assertNotSame("Expected connection be proxied", connection, connectionProxy);
+
+        // exit component context
+        connectionTrackingCoordinator.exit(oldConnectorInstanceContext);
+
+        // connection should not be in context
+        connectionManagerMap = componentContext.getConnectionManagerMap();
+        infos = (Set) connectionManagerMap.get(key1);
+        assertEquals("Expected no connection set for key1", null, infos);
+
+        // enter again, and close the handle
+        oldConnectorInstanceContext = connectionTrackingCoordinator.enter(componentContext);
+        assertNull("Expected old instance context to be null", oldConnectorInstanceContext);
+        connectionTrackingCoordinator.handleReleased(key1, connectionInfo, ConnectionReturnAction.DESTROY);
+        connectionTrackingCoordinator.exit(oldConnectorInstanceContext);
+
+        // use connection which will cause it to get a new handle if it is not closed
+        ConnectionTrackingCoordinator.ConnectionInvocationHandler invocationHandler = (ConnectionTrackingCoordinator.ConnectionInvocationHandler) Proxy.getInvocationHandler(connectionProxy);
+        assertEquals("connection.getString()", "ConnectionString", connectionProxy.getString());
+        assertTrue("Proxy should be connected", invocationHandler.isReleased());
+        assertSame("Expected connection.getUnmanaged() to be original connection", connection, connection.getUnmanaged());
+    }
+
+    public void testReassociateConnection() throws Exception {
+        // enter component context
+        ConnectorInstanceContextImpl componentContext = new ConnectorInstanceContextImpl(unshareableResources, applicationManagedSecurityResources);
+        ConnectorInstanceContext oldConnectorInstanceContext1 = connectionTrackingCoordinator.enter(componentContext);
+        assertNull("Expected old component context to be null", oldConnectorInstanceContext1);
+
+        // simulate create connection
+        ConnectionInfo connectionInfo = createConnectionInfo();
+        connectionTrackingCoordinator.handleObtained(key1, connectionInfo, false);
+
+        // connection should be in component instance context
+        Map connectionManagerMap = componentContext.getConnectionManagerMap();
+        Set infos = (Set) connectionManagerMap.get(key1);
+        assertNotNull("Expected one connections for key1", infos);
+        assertEquals("Expected one connection for key1", 1, infos.size());
+        assertTrue("Expected to get supplied ConnectionInfo from infos", connectionInfo == infos.iterator().next());
+
+        // verify handle
+        Object handle = connectionInfo.getConnectionHandle();
+        assertNotNull("Expected a handle from ConnectionInfo", handle);
+        assertTrue("Expected handle to be an instance of ConnectionImpl", handle instanceof ConnectionImpl);
+        ConnectionImpl connection = (ConnectionImpl) handle;
+        assertEquals("connection.getString()", "ConnectionString", connection.getString());
+
+        // verify proxy
+        Object proxy = connectionInfo.getConnectionProxy();
+        assertNotNull("Expected a proxy from ConnectionInfo", proxy);
+        assertTrue("Expected proxy to be an instance of Connection", proxy instanceof Connection);
+        Connection connectionProxy = (Connection) proxy;
+        assertEquals("connection.getString()", "ConnectionString", connectionProxy.getString());
+        assertSame("Expected connection.getUnmanaged() to be connectionImpl", connection, connectionProxy.getUnmanaged());
+        assertNotSame("Expected connection be proxied", connection, connectionProxy);
+
+
+        // exit outer component context
+        connectionTrackingCoordinator.exit(oldConnectorInstanceContext1);
+
+        // proxy should be disconnected
+        ConnectionTrackingCoordinator.ConnectionInvocationHandler invocationHandler = (ConnectionTrackingCoordinator.ConnectionInvocationHandler) Proxy.getInvocationHandler(connectionProxy);
+        assertTrue("Proxy should be disconnected", invocationHandler.isReleased());
+
+        // enter again
+        oldConnectorInstanceContext1 = connectionTrackingCoordinator.enter(componentContext);
+        assertNull("Expected old component context to be null", oldConnectorInstanceContext1);
+
+        // use connection to cause it to get a new handle
+        assertEquals("connection.getString()", "ConnectionString", connectionProxy.getString());
+        assertSame("Expected connection.getUnmanaged() to be original connection", connection, connection.getUnmanaged());
+        assertNotSame("Expected connection to not be original connection", connection, connectionProxy);
+
+        // destroy handle and exit context
+        connectionTrackingCoordinator.handleReleased(key1, connectionInfo, ConnectionReturnAction.DESTROY);
+        connectionTrackingCoordinator.exit(oldConnectorInstanceContext1);
+
+        // connection should not be in context
+        connectionManagerMap = componentContext.getConnectionManagerMap();
+        infos = (Set) connectionManagerMap.get(key1);
+        assertNull("Expected no connection set for key1", infos);
+
+        // use connection which will cause it to get a new handle if it is not closed
+        assertEquals("connection.getString()", "ConnectionString", connectionProxy.getString());
+        assertTrue("Proxy should be connected", invocationHandler.isReleased());
+        assertSame("Expected connection.getUnmanaged() to be original connection", connection, connection.getUnmanaged());
+
+    }
+
+    // some code calls the release method using a freshly constructed ContainerInfo without a proxy
+    // the ConnectionTrackingCoordinator must find the correct proxy and call releaseHandle or destroy
+    public void testReleaseNoProxy() throws Exception {
+        // enter component context
+        ConnectorInstanceContextImpl componentContext = new ConnectorInstanceContextImpl(unshareableResources, applicationManagedSecurityResources);
+        ConnectorInstanceContext oldConnectorInstanceContext1 = connectionTrackingCoordinator.enter(componentContext);
+        assertNull("Expected old component context to be null", oldConnectorInstanceContext1);
+
+        // simulate create connection
+        ConnectionInfo connectionInfo = createConnectionInfo();
+        connectionTrackingCoordinator.handleObtained(key1, connectionInfo, false);
+
+        // connection should be in component instance context
+        Map connectionManagerMap = componentContext.getConnectionManagerMap();
+        Set infos = (Set) connectionManagerMap.get(key1);
+        assertNotNull("Expected one connections for key1", infos);
+        assertEquals("Expected one connection for key1", 1, infos.size());
+        assertTrue("Expected to get supplied ConnectionInfo from infos", connectionInfo == infos.iterator().next());
+
+        // verify handle
+        Object handle = connectionInfo.getConnectionHandle();
+        assertNotNull("Expected a handle from ConnectionInfo", handle);
+        assertTrue("Expected handle to be an instance of ConnectionImpl", handle instanceof ConnectionImpl);
+        ConnectionImpl connection = (ConnectionImpl) handle;
+        assertEquals("connection.getString()", "ConnectionString", connection.getString());
+
+        // verify proxy
+        Object proxy = connectionInfo.getConnectionProxy();
+        assertNotNull("Expected a proxy from ConnectionInfo", proxy);
+        assertTrue("Expected proxy to be an instance of Connection", proxy instanceof Connection);
+        Connection connectionProxy = (Connection) proxy;
+        assertEquals("connection.getString()", "ConnectionString", connectionProxy.getString());
+        assertSame("Expected connection.getUnmanaged() to be connectionImpl", connection, connectionProxy.getUnmanaged());
+        assertNotSame("Expected connection be proxied", connection, connectionProxy);
+
+
+        // simulate handle release due to a event listener, which won't hav the proxy
+        connectionTrackingCoordinator.handleReleased(key1, createConnectionInfo(), ConnectionReturnAction.RETURN_HANDLE);
+
+        // proxy should be disconnected
+        ConnectionTrackingCoordinator.ConnectionInvocationHandler invocationHandler = (ConnectionTrackingCoordinator.ConnectionInvocationHandler) Proxy.getInvocationHandler(connectionProxy);
+        assertTrue("Proxy should be disconnected", invocationHandler.isReleased());
+
+        // use connection which will cause it to get a new handle if it is not closed
+        assertEquals("connection.getString()", "ConnectionString", connectionProxy.getString());
+        assertTrue("Proxy should be connected", invocationHandler.isReleased());
+        assertSame("Expected connection.getUnmanaged() to be original connection", connection, connection.getUnmanaged());
+
+        // exit outer component context
+        connectionTrackingCoordinator.exit(oldConnectorInstanceContext1);
+
+        // proxy should be disconnected
+        assertTrue("Proxy should be disconnected", invocationHandler.isReleased());
+
+        // connection should not be in context
+        connectionManagerMap = componentContext.getConnectionManagerMap();
+        infos = (Set) connectionManagerMap.get(key1);
+        assertNull("Expected no connection set for key1", infos);
+
+        // enter again
+        oldConnectorInstanceContext1 = connectionTrackingCoordinator.enter(componentContext);
+        assertNull("Expected old component context to be null", oldConnectorInstanceContext1);
+
+        // exit context
+        connectionTrackingCoordinator.exit(oldConnectorInstanceContext1);
+
+        // use connection which will cause it to get a new handle if it is not closed
+        assertEquals("connection.getString()", "ConnectionString", connectionProxy.getString());
+        assertTrue("Proxy should be connected", invocationHandler.isReleased());
+        assertSame("Expected connection.getUnmanaged() to be original connection", connection, connection.getUnmanaged());
+    }
+
+
+    public Subject mapSubject(Subject sourceSubject) {
+        return subject;
+    }
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+    }
+
+    public void returnConnection(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
+    }
+
+    public void destroy() {
+    }
+
+    private ConnectionInfo createConnectionInfo() {
+        ConnectionInfo ci = new ConnectionInfo(mci);
+        ci.setConnectionHandle(connection);
+        mci.addConnectionHandle(ci);
+        return ci;
+    }
+
+
+    public static interface Connection {
+        String getString();
+        Connection getUnmanaged();
+    }
+
+    public static class ConnectionImpl implements Connection {
+        private final String string;
+
+        public ConnectionImpl(String string) {
+            this.string = string;
+        }
+
+        public String getString() {
+            return string;
+        }
+
+        public Connection getUnmanaged() {
+            return this;
+        }
+    }
+
+    public static class MockManagedConnection implements ManagedConnection {
+           public Object getConnection(Subject defaultSubject, ConnectionRequestInfo connectionRequestInfo) throws ResourceException {
+               return null;
+           }
+
+           public void destroy() throws ResourceException {
+           }
+
+           public void cleanup() throws ResourceException {
+           }
+
+           public void associateConnection(Object object) throws ResourceException {
+           }
+
+           public void addConnectionEventListener(ConnectionEventListener connectionEventListener) {
+           }
+
+           public void removeConnectionEventListener(ConnectionEventListener connectionEventListener) {
+           }
+
+           public XAResource getXAResource() throws ResourceException {
+               return null;
+           }
+
+           public LocalTransaction getLocalTransaction() throws ResourceException {
+               return null;
+           }
+
+           public ManagedConnectionMetaData getMetaData() throws ResourceException {
+               return null;
+           }
+
+           public void setLogWriter(PrintWriter printWriter) throws ResourceException {
+           }
+
+           public PrintWriter getLogWriter() throws ResourceException {
+               return null;
+           }
+
+           public void dissociateConnections() throws ResourceException {
+           }
+       }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectionTrackingCoordinatorTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectionTrackingCoordinatorTest.java
new file mode 100644
index 0000000..a16e601
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/connectiontracking/ConnectionTrackingCoordinatorTest.java
@@ -0,0 +1,169 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectiontracking;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.resource.ResourceException;
+import javax.security.auth.Subject;
+
+import junit.framework.TestCase;
+import org.apache.geronimo.connector.outbound.ConnectionInfo;
+import org.apache.geronimo.connector.outbound.ConnectionInterceptor;
+import org.apache.geronimo.connector.outbound.ConnectionReturnAction;
+import org.apache.geronimo.connector.outbound.ConnectionTrackingInterceptor;
+import org.apache.geronimo.connector.outbound.ManagedConnectionInfo;
+import org.apache.geronimo.connector.outbound.GeronimoConnectionEventListener;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class ConnectionTrackingCoordinatorTest extends TestCase
+        implements ConnectionInterceptor {
+
+    private static final String name1 = "foo";
+    private static final String name2 = "bar";
+    private ConnectionTrackingCoordinator connectionTrackingCoordinator;
+    private ConnectionTrackingInterceptor key1;
+    private ConnectionTrackingInterceptor nestedKey;
+    private Subject subject = null;
+    private Set unshareableResources;
+    private Set applicationManagedSecurityResources;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        connectionTrackingCoordinator = new ConnectionTrackingCoordinator(false);
+        key1 = new ConnectionTrackingInterceptor(this, name1, connectionTrackingCoordinator);
+        nestedKey = new ConnectionTrackingInterceptor(this, name2, connectionTrackingCoordinator);
+        unshareableResources = new HashSet();
+        applicationManagedSecurityResources = new HashSet();
+    }
+
+    protected void tearDown() throws Exception {
+        connectionTrackingCoordinator = null;
+        key1 = null;
+        nestedKey = null;
+        super.tearDown();
+    }
+
+    public void testSimpleComponentContextLifecyle() throws Exception {
+        // enter component context
+        ConnectorInstanceContextImpl componentContext = new ConnectorInstanceContextImpl(unshareableResources, applicationManagedSecurityResources);
+        ConnectorInstanceContext oldConnectorInstanceContext = connectionTrackingCoordinator.enter(componentContext);
+        assertNull("Expected old instance context to be null", oldConnectorInstanceContext);
+
+        // simulate create connection
+        ConnectionInfo connectionInfo = newConnectionInfo();
+        connectionTrackingCoordinator.handleObtained(key1, connectionInfo, false);
+
+        // exit component context
+        connectionTrackingCoordinator.exit(oldConnectorInstanceContext);
+
+        // connection should be in component instance context
+        Map connectionManagerMap = componentContext.getConnectionManagerMap();
+        Set infos = (Set) connectionManagerMap.get(key1);
+        assertNotNull("Expected one connections for key1", infos);
+        assertEquals("Expected one connection for key1", 1, infos.size());
+        assertTrue("Expected to get supplied ConnectionInfo from infos", connectionInfo == infos.iterator().next());
+
+        // enter again, and close the handle
+        oldConnectorInstanceContext = connectionTrackingCoordinator.enter(componentContext);
+        assertNull("Expected old instance context to be null", oldConnectorInstanceContext);
+        connectionTrackingCoordinator.handleReleased(key1, connectionInfo, ConnectionReturnAction.DESTROY);
+        connectionTrackingCoordinator.exit(oldConnectorInstanceContext);
+
+        // connection should not be in context
+        connectionManagerMap = componentContext.getConnectionManagerMap();
+        infos = (Set) connectionManagerMap.get(key1);
+        assertEquals("Expected no connection set for key1", null, infos);
+    }
+
+    private ConnectionInfo newConnectionInfo() {
+        ManagedConnectionInfo mci = new ManagedConnectionInfo(null, null);
+        mci.setConnectionEventListener(new GeronimoConnectionEventListener(this, mci));
+        ConnectionInfo ci = new ConnectionInfo(mci);
+        ci.setConnectionHandle(new Object());
+        mci.addConnectionHandle(ci);
+        return ci;
+    }
+
+    public void testNestedComponentContextLifecyle() throws Exception {
+        // enter component context
+        ConnectorInstanceContextImpl componentContext1 = new ConnectorInstanceContextImpl(unshareableResources, applicationManagedSecurityResources);
+        ConnectorInstanceContext oldConnectorInstanceContext1 = connectionTrackingCoordinator.enter(componentContext1);
+        assertNull("Expected old component context to be null", oldConnectorInstanceContext1);
+
+        // simulate create connection
+        ConnectionInfo connectionInfo1 = newConnectionInfo();
+        connectionTrackingCoordinator.handleObtained(key1, connectionInfo1, false);
+
+        // enter another (nested) component context
+        ConnectorInstanceContextImpl nextedComponentContext = new ConnectorInstanceContextImpl(unshareableResources, applicationManagedSecurityResources);
+        ConnectorInstanceContext oldConnectorInstanceContext2 = connectionTrackingCoordinator.enter(nextedComponentContext);
+        assertTrue("Expected returned component context to be componentContext1", oldConnectorInstanceContext2 == componentContext1);
+
+        // simulate create connection in nested context
+        ConnectionInfo nestedConnectionInfo = newConnectionInfo();
+        connectionTrackingCoordinator.handleObtained(nestedKey, nestedConnectionInfo, false);
+
+        // exit nested component context
+        connectionTrackingCoordinator.exit(oldConnectorInstanceContext2);
+        Map nestedConnectionManagerMap = nextedComponentContext.getConnectionManagerMap();
+        Set nestedInfos = (Set) nestedConnectionManagerMap.get(nestedKey);
+        assertNotNull("Expected one connections for key2", nestedInfos);
+        assertEquals("Expected one connection for key2", 1, nestedInfos.size());
+        assertSame("Expected to get supplied ConnectionInfo from infos", nestedConnectionInfo, nestedInfos.iterator().next());
+        assertNull("Expected no connection for key1", nestedConnectionManagerMap.get(key1));
+
+
+        // exit outer component context
+        connectionTrackingCoordinator.exit(oldConnectorInstanceContext1);
+        Map connectionManagerMap = componentContext1.getConnectionManagerMap();
+        Set infos1 = (Set) connectionManagerMap.get(key1);
+        assertNotNull("Expected one connections for key1", infos1);
+        assertEquals("Expected one connection for key1", 1, infos1.size());
+        assertSame("Expected to get supplied ConnectionInfo from infos", connectionInfo1, infos1.iterator().next());
+        assertNull("Expected no connection for key2", connectionManagerMap.get(nestedKey));
+
+        // enter again, and close the handle
+        oldConnectorInstanceContext1 = connectionTrackingCoordinator.enter(componentContext1);
+        assertNull("Expected old component context to be null", oldConnectorInstanceContext1);
+        connectionTrackingCoordinator.handleReleased(key1, connectionInfo1, ConnectionReturnAction.DESTROY);
+        connectionTrackingCoordinator.exit(oldConnectorInstanceContext1);
+
+        // connection should not be in context
+        connectionManagerMap = componentContext1.getConnectionManagerMap();
+        infos1 = (Set) connectionManagerMap.get(key1);
+        assertNull("Expected no connection set for key1", infos1);
+    }
+
+    public Subject mapSubject(Subject sourceSubject) {
+        return subject;
+    }
+
+    public void getConnection(ConnectionInfo connectionInfo) throws ResourceException {
+    }
+
+    public void returnConnection(ConnectionInfo connectionInfo, ConnectionReturnAction connectionReturnAction) {
+    }
+
+    public void destroy() {        
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/connectiontracking/DefaultComponentInterceptor.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/connectiontracking/DefaultComponentInterceptor.java
new file mode 100644
index 0000000..d49a4b4
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/connectiontracking/DefaultComponentInterceptor.java
@@ -0,0 +1,47 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectiontracking;
+
+import org.apache.geronimo.connector.outbound.connectiontracking.ConnectorInstanceContext;
+import org.apache.geronimo.connector.outbound.connectiontracking.TrackedConnectionAssociator;
+
+/**
+ * Sample functionality for an interceptor that enables connection caching and obtaining
+ * connections outside a UserTransaction.
+ *
+ * @version $Rev$ $Date$
+ */
+public class DefaultComponentInterceptor implements DefaultInterceptor {
+
+    private final DefaultInterceptor next;
+    private final TrackedConnectionAssociator trackedConnectionAssociator;
+
+    public DefaultComponentInterceptor(DefaultInterceptor next, TrackedConnectionAssociator trackedConnectionAssociator) {
+        this.next = next;
+        this.trackedConnectionAssociator = trackedConnectionAssociator;
+    }
+
+    public Object invoke(ConnectorInstanceContext newConnectorInstanceContext) throws Throwable {
+        ConnectorInstanceContext oldConnectorInstanceContext = trackedConnectionAssociator.enter(newConnectorInstanceContext);
+        try {
+            return next.invoke(newConnectorInstanceContext);
+        } finally {
+            trackedConnectionAssociator.exit(oldConnectorInstanceContext);
+        }
+    }
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/connectiontracking/DefaultInterceptor.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/connectiontracking/DefaultInterceptor.java
new file mode 100644
index 0000000..1c94725
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/outbound/connectiontracking/DefaultInterceptor.java
@@ -0,0 +1,32 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.outbound.connectiontracking;
+
+import org.apache.geronimo.connector.outbound.connectiontracking.ConnectorInstanceContext;
+
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public interface DefaultInterceptor {
+
+    Object invoke(ConnectorInstanceContext newConnectorInstanceContext) throws Throwable;
+}
diff --git a/geronimo-connector/src/test/java/org/apache/geronimo/connector/work/PooledWorkManagerTest.java b/geronimo-connector/src/test/java/org/apache/geronimo/connector/work/PooledWorkManagerTest.java
new file mode 100644
index 0000000..6e2e2b4
--- /dev/null
+++ b/geronimo-connector/src/test/java/org/apache/geronimo/connector/work/PooledWorkManagerTest.java
@@ -0,0 +1,308 @@
+/**
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.connector.work;
+
+import java.lang.reflect.Constructor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import javax.resource.spi.work.ExecutionContext;
+import javax.resource.spi.work.Work;
+import javax.resource.spi.work.WorkEvent;
+import javax.resource.spi.work.WorkException;
+import javax.resource.spi.work.WorkListener;
+
+import junit.framework.TestCase;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.geronimo.transaction.manager.GeronimoTransactionManager;
+import org.apache.geronimo.transaction.manager.XAWork;
+
+/**
+ * Timing is crucial for this test case, which focuses on the synchronization
+ * specificities of the doWork, startWork and scheduleWork.
+ *
+ * @version $Rev$ $Date$
+ */
+public class PooledWorkManagerTest extends TestCase {
+
+    private GeronimoWorkManager workManager;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        
+        XAWork xaWork = new GeronimoTransactionManager();
+        int poolSize = 1;
+        int keepAliveTime = 30000;
+        ThreadPoolExecutor pool = new ThreadPoolExecutor(
+            poolSize, // core size
+            poolSize, // max size
+            keepAliveTime, TimeUnit.MILLISECONDS,
+            new SynchronousQueue());
+
+        pool.setRejectedExecutionHandler(new WaitWhenBlockedPolicy());
+        pool.setThreadFactory(new ThreadPoolThreadFactory("Connector Test", getClass().getClassLoader()));
+
+
+        workManager = new GeronimoWorkManager(pool, pool, pool, xaWork);
+        workManager.doStart();
+    }
+
+    private static class WaitWhenBlockedPolicy
+        implements RejectedExecutionHandler {
+        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) throws RejectedExecutionException {
+            try {
+                executor.getQueue().put(r);
+            }
+            catch (InterruptedException e) {
+                throw new RejectedExecutionException(e);
+            }
+        }
+    }
+    private static final class ThreadPoolThreadFactory implements ThreadFactory {
+        private final String poolName;
+        private final ClassLoader classLoader;
+
+        private int nextWorkerID = 0;
+
+        public ThreadPoolThreadFactory(String poolName, ClassLoader classLoader) {
+            this.poolName = poolName;
+            this.classLoader = classLoader;
+        }
+
+        public Thread newThread(Runnable arg0) {
+            Thread thread = new Thread(arg0, poolName + " " + getNextWorkerID());
+            thread.setContextClassLoader(classLoader);
+            return thread;
+        }
+
+        private synchronized int getNextWorkerID() {
+            return nextWorkerID++;
+        }
+    }
+
+    public void testDoWork() throws Exception {
+        int nbThreads = 2;
+        AbstractDummyWork threads[] = helperTest(DummyDoWork.class, nbThreads, 500, 600);
+        int nbStopped = 0;
+        int nbTimeout = 0;
+        for (int i = 0; i < threads.length; i++) {
+            if ( null != threads[i].listener.completedEvent ) {
+                nbStopped++;
+            } else if ( null != threads[i].listener.rejectedEvent ) {
+                assertTrue("Should be a time out exception.",
+                    threads[i].listener.rejectedEvent.getException().
+                    getErrorCode() == WorkException.START_TIMED_OUT);
+                nbTimeout++;
+            } else {
+                fail("WORK_COMPLETED or WORK_REJECTED expected");
+            }
+        }
+        assertEquals("Wrong number of works in the WORK_COMPLETED state", 1, nbStopped);
+        assertEquals("Wrong number of works in the START_TIMED_OUT state", 1, nbTimeout);
+    }
+
+    public void testStartWork() throws Exception {
+        AbstractDummyWork threads[] = helperTest(DummyStartWork.class, 2, 10000, 100);
+        int nbStopped = 0;
+        int nbStarted = 0;
+        for (int i = 0; i < threads.length; i++) {
+            if ( null != threads[i].listener.completedEvent ) {
+                nbStopped++;
+            } else if ( null != threads[i].listener.startedEvent ) {
+                nbStarted++;
+            } else {
+                fail("WORK_COMPLETED or WORK_STARTED expected");
+            }
+        }
+        assertEquals("At least one work should be in the WORK_COMPLETED state.", 1, nbStopped);
+        assertEquals("At least one work should be in the WORK_STARTED state.", 1, nbStarted);
+    }
+
+    public void testScheduleWork() throws Exception {
+        AbstractDummyWork threads[] =
+            helperTest(DummyScheduleWork.class, 3, 10000, 100);
+        int nbAccepted = 0;
+        int nbStarted = 0;
+
+        for (int i = 0; i < threads.length; i++) {
+            if ( null != threads[i].listener.acceptedEvent ) {
+                nbAccepted++;
+            } else if ( null != threads[i].listener.startedEvent ) {
+                nbStarted++;
+            } else {
+                fail("WORK_ACCEPTED or WORK_STARTED expected");
+            }
+        }
+
+        assertTrue("At least one work should be in the WORK_ACCEPTED state.", nbAccepted > 0);
+    }
+
+    public void testLifecycle() throws Exception {
+        testDoWork();
+        workManager.doStop();
+        workManager.doStart();
+        testDoWork();
+    }
+
+    private AbstractDummyWork[] helperTest(Class aWork, int nbThreads,
+                                           int aTimeOut, int aTempo)
+        throws Exception {
+        Constructor constructor = aWork.getConstructor(
+            new Class[]{PooledWorkManagerTest.class, String.class,
+                int.class, int.class});
+        AbstractDummyWork rarThreads[] = new AbstractDummyWork[nbThreads];
+        for (int i = 0; i < nbThreads; i++) {
+            rarThreads[i] = (AbstractDummyWork)
+                constructor.newInstance(
+                    new Object[]{this, "Work" + i,
+                        new Integer(aTimeOut), new Integer(aTempo)});
+        }
+        for (int i = 0; i < nbThreads; i++) {
+            rarThreads[i].start();
+        }
+        for (int i = 0; i < nbThreads; i++) {
+            rarThreads[i].join();
+        }
+        return rarThreads;
+    }
+
+    public abstract class AbstractDummyWork extends Thread {
+        public final DummyWorkListener listener;
+        protected final  String name;
+        private final int timeout;
+        private final int tempo;
+        public AbstractDummyWork(String aName, int aTimeOut, int aTempo) {
+            listener = new DummyWorkListener();
+            timeout = aTimeOut;
+            tempo = aTempo;
+            name = aName;
+        }
+        public void run() {
+            try {
+                perform(new DummyWork(name, tempo), timeout, null, listener);
+            } catch (Exception e) {
+//                log.debug(e.getMessage(), e);
+            }
+        }
+
+        protected abstract void perform(Work work,
+                                        long startTimeout,
+                                        ExecutionContext execContext,
+                                        WorkListener workListener) throws Exception;
+    }
+
+    public class DummyDoWork extends AbstractDummyWork {
+        public DummyDoWork(String aName, int aTimeOut, int aTempo) {
+            super(aName, aTimeOut, aTempo);
+        }
+
+        protected void perform(Work work,
+                               long startTimeout,
+                               ExecutionContext execContext,
+                               WorkListener workListener) throws Exception {
+            workManager.doWork(work, startTimeout, execContext, workListener);
+        }
+    }
+
+    public class DummyStartWork extends AbstractDummyWork {
+        public DummyStartWork(String aName, int aTimeOut, int aTempo) {
+            super(aName, aTimeOut, aTempo);
+        }
+
+        protected void perform(Work work,
+                               long startTimeout,
+                               ExecutionContext execContext,
+                               WorkListener workListener) throws Exception {
+            workManager.startWork(work, startTimeout, execContext, workListener);
+        }
+    }
+
+    public class DummyScheduleWork extends AbstractDummyWork {
+        public DummyScheduleWork(String aName, int aTimeOut, int aTempo) {
+            super(aName, aTimeOut, aTempo);
+        }
+
+        protected void perform(Work work,
+                               long startTimeout,
+                               ExecutionContext execContext,
+                               WorkListener workListener) throws Exception {
+            workManager.scheduleWork(work, startTimeout, execContext, workListener);
+        }
+    }
+
+    public static class DummyWork implements Work {
+        private Log log = LogFactory.getLog(getClass());
+
+        private final String name;
+        private final int tempo;
+
+        public DummyWork(String aName, int aTempo) {
+            name = aName;
+            tempo = aTempo;
+        }
+
+        public void release() {
+        }
+
+        public void run() {
+            try {
+                Thread.sleep(tempo);
+            } catch (InterruptedException e) {
+                log.debug(e.getMessage(), e);
+            }
+        }
+
+        public String toString() {
+            return name;
+        }
+    }
+
+    public static class DummyWorkListener implements WorkListener {
+        private Log log = LogFactory.getLog(getClass());
+
+        public WorkEvent acceptedEvent;
+        public WorkEvent rejectedEvent;
+        public WorkEvent startedEvent;
+        public WorkEvent completedEvent;
+
+        public void workAccepted(WorkEvent e) {
+            acceptedEvent = e;
+            log.debug("accepted: " + e);
+        }
+
+        public void workRejected(WorkEvent e) {
+            rejectedEvent = e;
+            log.debug("rejected: " + e);
+        }
+
+        public void workStarted(WorkEvent e) {
+            startedEvent = e;
+            log.debug("started: " + e);
+        }
+
+        public void workCompleted(WorkEvent e) {
+            completedEvent = e;
+            log.debug("completed: " + e);
+        }
+    }
+}
diff --git a/geronimo-transaction/LICENSE.txt b/geronimo-transaction/LICENSE.txt
new file mode 100644
index 0000000..6b0b127
--- /dev/null
+++ b/geronimo-transaction/LICENSE.txt
@@ -0,0 +1,203 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
+
diff --git a/geronimo-transaction/NOTICE.txt b/geronimo-transaction/NOTICE.txt
new file mode 100644
index 0000000..3b4090d
--- /dev/null
+++ b/geronimo-transaction/NOTICE.txt
@@ -0,0 +1,6 @@
+Apache Geronimo 
+Copyright 2003-2006 The Apache Software Foundation
+
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
+
diff --git a/geronimo-transaction/pom.xml b/geronimo-transaction/pom.xml
new file mode 100644
index 0000000..007709c
--- /dev/null
+++ b/geronimo-transaction/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You 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.
+-->
+
+<!-- $Rev$ $Date$ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.geronimo.genesis.config</groupId>
+        <artifactId>project-config</artifactId>
+        <version>1.2-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.apache.geronimo.components</groupId>
+    <artifactId>geronimo-transaction</artifactId>
+    <version>2.0-SNAPSHOT</version>
+    <name>Geronimo :: Transaction</name>
+    
+    <dependencies>
+
+            <dependency>
+                <groupId>commons-logging</groupId>
+                <artifactId>commons-logging</artifactId>
+                <version>1.0.4</version>
+            </dependency>
+
+
+
+<!-- 
+       <dependency>
+            <groupId>org.apache.geronimo.modules</groupId>
+            <artifactId>geronimo-core</artifactId>
+            <version>${version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.geronimo.modules</groupId>
+            <artifactId>geronimo-j2ee</artifactId>
+            <version>${version}</version>
+        </dependency>
+-->
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-jta_1.1_spec</artifactId>
+            <version>1.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-j2ee-connector_1.5_spec</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.objectweb.howl</groupId>
+            <artifactId>howl</artifactId>
+            <version>1.0.1-1</version>
+        </dependency>
+    </dependencies>
+    
+</project>
+
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/GeronimoUserTransaction.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/GeronimoUserTransaction.java
new file mode 100644
index 0000000..e2c180e
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/GeronimoUserTransaction.java
@@ -0,0 +1,76 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction;
+
+import java.io.Serializable;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.TransactionManager;
+import javax.transaction.UserTransaction;
+
+public final class GeronimoUserTransaction implements UserTransaction, Serializable {
+    private static final long serialVersionUID = -7524804683512228998L;
+    private transient TransactionManager transactionManager;
+
+    public GeronimoUserTransaction(TransactionManager transactionManager) {
+        this.transactionManager = transactionManager;
+    }
+
+    boolean isActive() {
+        return transactionManager != null;
+    }
+
+    public void setTransactionManager(TransactionManager transactionManager) {
+        if (this.transactionManager == null) {
+            this.transactionManager = transactionManager;
+        } else if (this.transactionManager != transactionManager) {
+            throw new IllegalStateException("This user transaction is already associated with another transaction manager");
+        }
+    }
+
+
+    public int getStatus() throws SystemException {
+        return transactionManager.getStatus();
+    }
+
+    public void setRollbackOnly() throws IllegalStateException, SystemException {
+        transactionManager.setRollbackOnly();
+    }
+
+    public void setTransactionTimeout(int seconds) throws SystemException {
+        if (seconds < 0) {
+            throw new SystemException("transaction timeout must be positive or 0, not " + seconds);
+        }
+        transactionManager.setTransactionTimeout(seconds);
+    }
+
+    public void begin() throws NotSupportedException, SystemException {
+        transactionManager.begin();
+    }
+
+    public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException, RollbackException, SecurityException, SystemException {
+        transactionManager.commit();
+    }
+
+    public void rollback() throws IllegalStateException, SecurityException, SystemException {
+        transactionManager.rollback();
+    }
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/log/HOWLLog.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/log/HOWLLog.java
new file mode 100644
index 0000000..d5e911b
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/log/HOWLLog.java
@@ -0,0 +1,405 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.log;
+
+import java.io.IOException;
+import java.io.File;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import javax.transaction.xa.Xid;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.geronimo.transaction.manager.LogException;
+import org.apache.geronimo.transaction.manager.Recovery;
+import org.apache.geronimo.transaction.manager.TransactionBranchInfo;
+import org.apache.geronimo.transaction.manager.TransactionBranchInfoImpl;
+import org.apache.geronimo.transaction.manager.TransactionLog;
+import org.apache.geronimo.transaction.manager.XidFactory;
+import org.objectweb.howl.log.Configuration;
+import org.objectweb.howl.log.LogClosedException;
+import org.objectweb.howl.log.LogConfigurationException;
+import org.objectweb.howl.log.LogFileOverflowException;
+import org.objectweb.howl.log.LogRecord;
+import org.objectweb.howl.log.LogRecordSizeException;
+import org.objectweb.howl.log.LogRecordType;
+import org.objectweb.howl.log.ReplayListener;
+import org.objectweb.howl.log.xa.XACommittingTx;
+import org.objectweb.howl.log.xa.XALogRecord;
+import org.objectweb.howl.log.xa.XALogger;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class HOWLLog implements TransactionLog {
+//    static final byte PREPARE = 1;
+    //these are used as debugging aids only
+    private static final byte COMMIT = 2;
+    private static final byte ROLLBACK = 3;
+
+    static final String[] TYPE_NAMES = {null, "PREPARE", "COMMIT", "ROLLBACK"};
+
+    private static final Log log = LogFactory.getLog(HOWLLog.class);
+
+    private File serverBaseDir;
+    private String logFileDir;
+
+    private final XidFactory xidFactory;
+
+    private final XALogger logger;
+    private final Configuration configuration = new Configuration();
+    private boolean started = false;
+    private HashMap recovered;
+
+    public HOWLLog(String bufferClassName,
+                   int bufferSize,
+                   boolean checksumEnabled,
+                   boolean adler32Checksum,
+                   int flushSleepTimeMilliseconds,
+                   String logFileDir,
+                   String logFileExt,
+                   String logFileName,
+                   int maxBlocksPerFile,
+                   int maxBuffers,
+                   int maxLogFiles,
+                   int minBuffers,
+                   int threadsWaitingForceThreshold,
+                   XidFactory xidFactory,
+                   File serverBaseDir) throws IOException, LogConfigurationException {
+        this.serverBaseDir = serverBaseDir;
+        setBufferClassName(bufferClassName);
+        setBufferSizeKBytes(bufferSize);
+        setChecksumEnabled(checksumEnabled);
+        setAdler32Checksum(adler32Checksum);
+        setFlushSleepTimeMilliseconds(flushSleepTimeMilliseconds);
+        //setLogFileDir(logFileDir);
+        this.logFileDir = logFileDir;
+        setLogFileExt(logFileExt);
+        setLogFileName(logFileName);
+        setMaxBlocksPerFile(maxBlocksPerFile);
+        setMaxBuffers(maxBuffers);
+        setMaxLogFiles(maxLogFiles);
+        setMinBuffers(minBuffers);
+        setThreadsWaitingForceThreshold(threadsWaitingForceThreshold);
+        this.xidFactory = xidFactory;
+        this.logger = new XALogger(configuration);
+    }
+
+    public String getLogFileDir() {
+        return logFileDir;
+    }
+
+    public void setLogFileDir(String logDirName) {
+        File logDir = new File(logDirName);
+        if (!logDir.isAbsolute()) {
+            logDir = new File(serverBaseDir, logDirName);
+        }
+
+        this.logFileDir = logDirName;
+        if (started) {
+            configuration.setLogFileDir(logDir.getAbsolutePath());
+        }
+    }
+
+    public String getLogFileExt() {
+        return configuration.getLogFileExt();
+    }
+
+    public void setLogFileExt(String logFileExt) {
+        configuration.setLogFileExt(logFileExt);
+    }
+
+    public String getLogFileName() {
+        return configuration.getLogFileName();
+    }
+
+    public void setLogFileName(String logFileName) {
+        configuration.setLogFileName(logFileName);
+    }
+
+    public boolean isChecksumEnabled() {
+        return configuration.isChecksumEnabled();
+    }
+
+    public void setChecksumEnabled(boolean checksumOption) {
+        configuration.setChecksumEnabled(checksumOption);
+    }
+
+    public boolean isAdler32ChecksumEnabled() {
+        return configuration.isAdler32ChecksumEnabled();
+    }
+
+    public void setAdler32Checksum(boolean checksumOption) {
+        configuration.setAdler32Checksum(checksumOption);
+    }
+
+    public int getBufferSizeKBytes() {
+        return configuration.getBufferSize();
+    }
+
+    public void setBufferSizeKBytes(int bufferSize) throws LogConfigurationException {
+        configuration.setBufferSize(bufferSize);
+    }
+
+    public String getBufferClassName() {
+        return configuration.getBufferClassName();
+    }
+
+    public void setBufferClassName(String bufferClassName) {
+        configuration.setBufferClassName(bufferClassName);
+    }
+
+    public int getMaxBuffers() {
+        return configuration.getMaxBuffers();
+    }
+
+    public void setMaxBuffers(int maxBuffers) throws LogConfigurationException {
+        configuration.setMaxBuffers(maxBuffers);
+    }
+
+    public int getMinBuffers() {
+        return configuration.getMinBuffers();
+    }
+
+    public void setMinBuffers(int minBuffers) throws LogConfigurationException {
+        configuration.setMinBuffers(minBuffers);
+    }
+
+    public int getFlushSleepTimeMilliseconds() {
+        return configuration.getFlushSleepTime();
+    }
+
+    public void setFlushSleepTimeMilliseconds(int flushSleepTime) {
+        configuration.setFlushSleepTime(flushSleepTime);
+    }
+
+    public int getThreadsWaitingForceThreshold() {
+        return configuration.getThreadsWaitingForceThreshold();
+    }
+
+    public void setThreadsWaitingForceThreshold(int threadsWaitingForceThreshold) {
+        configuration.setThreadsWaitingForceThreshold(threadsWaitingForceThreshold == -1 ? Integer.MAX_VALUE : threadsWaitingForceThreshold);
+    }
+
+    public int getMaxBlocksPerFile() {
+        return configuration.getMaxBlocksPerFile();
+    }
+
+    public void setMaxBlocksPerFile(int maxBlocksPerFile) {
+        configuration.setMaxBlocksPerFile(maxBlocksPerFile == -1 ? Integer.MAX_VALUE : maxBlocksPerFile);
+    }
+
+    public int getMaxLogFiles() {
+        return configuration.getMaxLogFiles();
+    }
+
+    public void setMaxLogFiles(int maxLogFiles) {
+        configuration.setMaxLogFiles(maxLogFiles);
+    }
+
+    public void doStart() throws Exception {
+        started = true;
+        setLogFileDir(logFileDir);
+        log.debug("Initiating transaction manager recovery");
+        recovered = new HashMap();
+
+        logger.open(null);
+
+        ReplayListener replayListener = new GeronimoReplayListener(xidFactory, recovered);
+        logger.replayActiveTx(replayListener);
+
+        log.debug("In doubt transactions recovered from log");
+    }
+
+    public void doStop() throws Exception {
+        started = false;
+        logger.close();
+        recovered = null;
+    }
+
+    public void doFail() {
+    }
+
+    public void begin(Xid xid) throws LogException {
+    }
+
+    public Object prepare(Xid xid, List branches) throws LogException {
+        int branchCount = branches.size();
+        byte[][] data = new byte[3 + 2 * branchCount][];
+        data[0] = intToBytes(xid.getFormatId());
+        data[1] = xid.getGlobalTransactionId();
+        data[2] = xid.getBranchQualifier();
+        int i = 3;
+        for (Iterator iterator = branches.iterator(); iterator.hasNext();) {
+            TransactionBranchInfo transactionBranchInfo = (TransactionBranchInfo) iterator.next();
+            data[i++] = transactionBranchInfo.getBranchXid().getBranchQualifier();
+            data[i++] = transactionBranchInfo.getResourceName().getBytes();
+        }
+        try {
+            XACommittingTx committingTx = logger.putCommit(data);
+            return committingTx;
+        } catch (LogClosedException e) {
+            throw (IllegalStateException) new IllegalStateException().initCause(e);
+        } catch (LogRecordSizeException e) {
+            throw (IllegalStateException) new IllegalStateException().initCause(e);
+        } catch (LogFileOverflowException e) {
+            throw (IllegalStateException) new IllegalStateException().initCause(e);
+        } catch (InterruptedException e) {
+            throw (IllegalStateException) new IllegalStateException().initCause(e);
+        } catch (IOException e) {
+            throw new LogException(e);
+        }
+    }
+
+    public void commit(Xid xid, Object logMark) throws LogException {
+        //the data is theoretically unnecessary but is included to help with debugging and because HOWL currently requires it.
+        byte[][] data = new byte[4][];
+        data[0] = new byte[]{COMMIT};
+        data[1] = intToBytes(xid.getFormatId());
+        data[2] = xid.getGlobalTransactionId();
+        data[3] = xid.getBranchQualifier();
+        try {
+            logger.putDone(data, (XACommittingTx) logMark);
+//            logger.putDone(null, (XACommittingTx) logMark);
+        } catch (LogClosedException e) {
+            throw (IllegalStateException) new IllegalStateException().initCause(e);
+        } catch (LogRecordSizeException e) {
+            throw (IllegalStateException) new IllegalStateException().initCause(e);
+        } catch (LogFileOverflowException e) {
+            throw (IllegalStateException) new IllegalStateException().initCause(e);
+        } catch (InterruptedException e) {
+            throw (IllegalStateException) new IllegalStateException().initCause(e);
+        } catch (IOException e) {
+            throw new LogException(e);
+        }
+    }
+
+    public void rollback(Xid xid, Object logMark) throws LogException {
+        //the data is theoretically unnecessary but is included to help with debugging and because HOWL currently requires it.
+        byte[][] data = new byte[4][];
+        data[0] = new byte[]{ROLLBACK};
+        data[1] = intToBytes(xid.getFormatId());
+        data[2] = xid.getGlobalTransactionId();
+        data[3] = xid.getBranchQualifier();
+        try {
+            logger.putDone(data, (XACommittingTx) logMark);
+//            logger.putDone(null, (XACommittingTx) logMark);
+        } catch (LogClosedException e) {
+            throw (IllegalStateException) new IllegalStateException().initCause(e);
+        } catch (LogRecordSizeException e) {
+            throw (IllegalStateException) new IllegalStateException().initCause(e);
+        } catch (LogFileOverflowException e) {
+            throw (IllegalStateException) new IllegalStateException().initCause(e);
+        } catch (InterruptedException e) {
+            throw (IllegalStateException) new IllegalStateException().initCause(e);
+        } catch (IOException e) {
+            throw new LogException(e);
+        }
+    }
+
+    public Collection recover(XidFactory xidFactory) throws LogException {
+        log.debug("Initiating transaction manager recovery");
+        Map recovered = new HashMap();
+        ReplayListener replayListener = new GeronimoReplayListener(xidFactory, recovered);
+        logger.replayActiveTx(replayListener);
+        log.debug("In doubt transactions recovered from log");
+        return recovered.values();
+    }
+
+    public String getXMLStats() {
+        return logger.getStats();
+    }
+
+    public int getAverageForceTime() {
+        return 0;//logger.getAverageForceTime();
+    }
+
+    public int getAverageBytesPerForce() {
+        return 0;//logger.getAverageBytesPerForce();
+    }
+
+    private byte[] intToBytes(int formatId) {
+        byte[] buffer = new byte[4];
+        buffer[0] = (byte) (formatId >> 24);
+        buffer[1] = (byte) (formatId >> 16);
+        buffer[2] = (byte) (formatId >> 8);
+        buffer[3] = (byte) (formatId >> 0);
+        return buffer;
+    }
+
+    private int bytesToInt(byte[] buffer) {
+        return ((int) buffer[0]) << 24 + ((int) buffer[1]) << 16 + ((int) buffer[2]) << 8 + ((int) buffer[3]) << 0;
+    }
+
+    private class GeronimoReplayListener implements ReplayListener {
+
+        private final XidFactory xidFactory;
+        private final Map recoveredTx;
+
+        public GeronimoReplayListener(XidFactory xidFactory, Map recoveredTx) {
+            this.xidFactory = xidFactory;
+            this.recoveredTx = recoveredTx;
+        }
+
+        public void onRecord(LogRecord plainlr) {
+            XALogRecord lr = (XALogRecord) plainlr;
+            short recordType = lr.type;
+            XACommittingTx tx = lr.getTx();
+            if (recordType == LogRecordType.XACOMMIT) {
+
+                byte[][] data = tx.getRecord();
+
+                assert data[0].length == 4;
+                int formatId = bytesToInt(data[1]);
+                byte[] globalId = data[1];
+                byte[] branchId = data[2];
+                Xid masterXid = xidFactory.recover(formatId, globalId, branchId);
+
+                Recovery.XidBranchesPair xidBranchesPair = new Recovery.XidBranchesPair(masterXid, tx);
+                recoveredTx.put(masterXid, xidBranchesPair);
+                log.debug("recovered prepare record for master xid: " + masterXid);
+                for (int i = 3; i < data.length; i += 2) {
+                    byte[] branchBranchId = data[i];
+                    String name = new String(data[i + 1]);
+
+                    Xid branchXid = xidFactory.recover(formatId, globalId, branchBranchId);
+                    TransactionBranchInfoImpl branchInfo = new TransactionBranchInfoImpl(branchXid, name);
+                    xidBranchesPair.addBranch(branchInfo);
+                    log.debug("recovered branch for resource manager, branchId " + name + ", " + branchXid);
+                }
+            } else {
+                if(recordType != LogRecordType.END_OF_LOG) { // This value crops up every time the server is started
+                    log.warn("Received unexpected log record: " + lr +" ("+recordType+")");
+                }
+            }
+        }
+
+        public void onError(org.objectweb.howl.log.LogException exception) {
+            log.error("Error during recovery: ", exception);
+        }
+
+        public LogRecord getLogRecord() {
+            //TODO justify this size estimate
+            return new LogRecord(10 * 2 * Xid.MAXBQUALSIZE);
+        }
+
+    }
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/log/UnrecoverableLog.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/log/UnrecoverableLog.java
new file mode 100644
index 0000000..7e9bc7e
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/log/UnrecoverableLog.java
@@ -0,0 +1,66 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.transaction.xa.Xid;
+
+import org.apache.geronimo.transaction.manager.LogException;
+import org.apache.geronimo.transaction.manager.TransactionLog;
+import org.apache.geronimo.transaction.manager.XidFactory;
+
+/**
+ * A log sink that doesn't actually do anything.
+ * Not recommended for production use as heuristic recovery will be needed if
+ * the transaction coordinator dies.
+ *
+ * @version $Rev$ $Date$
+ */
+public class UnrecoverableLog implements TransactionLog {
+    public void begin(Xid xid) throws LogException {
+    }
+
+    public Object prepare(Xid xid, List branches) throws LogException {
+        return null;
+    }
+
+    public void commit(Xid xid, Object logMark) throws LogException {
+    }
+
+    public void rollback(Xid xid, Object logMark) throws LogException {
+    }
+
+    public Collection recover(XidFactory xidFactory) throws LogException {
+        return new ArrayList();
+    }
+
+    public String getXMLStats() {
+        return null;
+    }
+
+    public int getAverageForceTime() {
+        return 0;
+    }
+
+    public int getAverageBytesPerForce() {
+        return 0;
+    }
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/log/XidImpl2.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/log/XidImpl2.java
new file mode 100644
index 0000000..10cffde
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/log/XidImpl2.java
@@ -0,0 +1,150 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.log;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import javax.transaction.xa.Xid;
+
+/**
+ * Unique id for a transaction.  This implementation is backed by a single byte buffer
+ * so can do less copying than one backed by several byte buffers for the different components.
+ *
+ * @version $Rev$ $Date$
+ */
+public class XidImpl2 implements Xid, Serializable {
+    private static int HEADER_SIZE = 4;
+    private static int ACTION_POS = 0;
+    private static int GLOBALID_SIZE_POS = 1;
+    private static int BRANCHID_SIZE_POS = 2;
+    //3 unused
+    private static int FORMAT_ID = 0x4765526f;  // Gero
+    private static int FORMAT_SIZE = 4;
+
+    private static byte[] FORMAT_ID_BYTES = "Gero".getBytes();
+
+    private final byte[] buffer = new byte[HEADER_SIZE + FORMAT_SIZE + Xid.MAXGTRIDSIZE + Xid.MAXBQUALSIZE];
+    private int hash;
+    private Object key;
+
+    /**
+     * Constructor taking a global id (for the main transaction)
+     * @param globalId the global transaction id
+     */
+    public XidImpl2(byte[] globalId) {
+        System.arraycopy(FORMAT_ID_BYTES, 0, buffer, HEADER_SIZE, FORMAT_SIZE);
+        buffer[GLOBALID_SIZE_POS] = (byte) globalId.length;
+        System.arraycopy(globalId, 0, buffer, HEADER_SIZE + FORMAT_SIZE, Xid.MAXGTRIDSIZE);
+
+        //this.hash = hash(buffer);
+    }
+
+    /**
+     * Constructor for a branch id
+     * @param global the xid of the global transaction this branch belongs to
+     * @param branch the branch id
+     */
+    public XidImpl2(Xid global, byte[] branch) {
+        if (global instanceof XidImpl2) {
+            System.arraycopy(((XidImpl2) global).buffer, 0, buffer, 0, HEADER_SIZE + FORMAT_SIZE + Xid.MAXGTRIDSIZE);
+        } else {
+            System.arraycopy(FORMAT_ID_BYTES, 0, buffer, HEADER_SIZE, FORMAT_SIZE);
+            byte[] globalId = global.getGlobalTransactionId();
+            System.arraycopy(globalId, 0, buffer, HEADER_SIZE + FORMAT_SIZE, globalId.length);
+        }
+        buffer[BRANCHID_SIZE_POS] = (byte) branch.length;
+        System.arraycopy(branch, 0, buffer, HEADER_SIZE + FORMAT_SIZE + Xid.MAXGTRIDSIZE, Xid.MAXBQUALSIZE);
+        //hash = hash(buffer);
+    }
+
+    public XidImpl2(int formatId, byte[] globalId, byte[] branch) {
+        //todo this is wrong, it ignores formatId supplied.  Maybe this is ok?
+        System.arraycopy(FORMAT_ID_BYTES, 0, buffer, HEADER_SIZE, FORMAT_SIZE);
+        System.arraycopy(globalId, 0, buffer, HEADER_SIZE + FORMAT_SIZE, globalId.length);
+        buffer[BRANCHID_SIZE_POS] = (byte) branch.length;
+        System.arraycopy(branch, 0, buffer, HEADER_SIZE + FORMAT_SIZE + Xid.MAXGTRIDSIZE, Xid.MAXBQUALSIZE);
+        //hash = hash(buffer);
+    }
+
+    private int hash(byte[] id) {
+        int hash = 0;
+        for (int i = 0; i < id.length; i++) {
+            hash = (hash * 37) + id[i];
+        }
+        return hash;
+    }
+
+    public int getFormatId() {
+        return FORMAT_ID;
+    }
+
+    public byte[] getGlobalTransactionId() {
+        byte[] globalId = new byte[buffer[GLOBALID_SIZE_POS]];
+        System.arraycopy(buffer, HEADER_SIZE + FORMAT_SIZE, globalId, 0, buffer[GLOBALID_SIZE_POS]);
+        return globalId;
+    }
+
+    public byte[] getBranchQualifier() {
+        byte[] branchId = new byte[buffer[BRANCHID_SIZE_POS]];
+        System.arraycopy(buffer, HEADER_SIZE + FORMAT_SIZE + Xid.MAXGTRIDSIZE, branchId, 0, buffer[BRANCHID_SIZE_POS]);
+        return branchId;
+    }
+
+    public boolean equals(Object obj) {
+        if (obj instanceof XidImpl2 == false) {
+            return false;
+        }
+        XidImpl2 other = (XidImpl2) obj;
+        return Arrays.equals(buffer, other.buffer);
+    }
+
+    public int hashCode() {
+        if (hash == 0) {
+            hash = hash(buffer);
+        }
+        return hash;
+    }
+
+    public String toString() {
+        StringBuffer s = new StringBuffer("[formatId=Gero,");
+        s.append("globalId=");
+        for (int i = FORMAT_SIZE; i < FORMAT_SIZE + Xid.MAXGTRIDSIZE; i++) {
+            s.append(Integer.toHexString(buffer[i]));
+        }
+        s.append(",branchId=");
+        for (int i = FORMAT_SIZE + Xid.MAXGTRIDSIZE; i < buffer.length; i++) {
+            s.append(Integer.toHexString(buffer[i]));
+        }
+        s.append("]");
+        return s.toString();
+    }
+
+    byte[] getBuffer(byte action) {
+        buffer[ACTION_POS] = action;
+        return buffer;
+    }
+
+    public void setKey(Object key) {
+        this.key = key;
+    }
+
+    public Object getKey() {
+        return key;
+    }
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/Closeable.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/Closeable.java
new file mode 100644
index 0000000..b0c8770
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/Closeable.java
@@ -0,0 +1,27 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public interface Closeable {
+
+    void close();
+
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/GeronimoTransactionManager.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/GeronimoTransactionManager.java
new file mode 100644
index 0000000..a6fc4b5
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/GeronimoTransactionManager.java
@@ -0,0 +1,203 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.transaction.manager;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import javax.resource.spi.XATerminator;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class GeronimoTransactionManager extends TransactionManagerImpl implements XATerminator, XAWork {
+    private final Map importedTransactions = new HashMap();
+    private boolean isInRecovery = false;
+
+    public GeronimoTransactionManager() throws XAException {
+    }
+
+    public GeronimoTransactionManager(int defaultTransactionTimeoutSeconds) throws XAException {
+        super(defaultTransactionTimeoutSeconds);
+    }
+
+    public GeronimoTransactionManager(int defaultTransactionTimeoutSeconds, TransactionLog transactionLog) throws XAException {
+        super(defaultTransactionTimeoutSeconds, transactionLog);
+    }
+
+    public GeronimoTransactionManager(int defaultTransactionTimeoutSeconds, XidFactory xidFactory, TransactionLog transactionLog) throws XAException {
+        super(defaultTransactionTimeoutSeconds, xidFactory, transactionLog);
+    }
+
+    /**
+     * @see javax.resource.spi.XATerminator#commit(javax.transaction.xa.Xid, boolean)
+     */
+    public void commit(Xid xid, boolean onePhase) throws XAException {
+        Transaction importedTransaction;
+        synchronized (importedTransactions) {
+            importedTransaction = (Transaction) importedTransactions.remove(xid);
+        }
+        if (importedTransaction == null) {
+            throw new XAException("No imported transaction for xid: " + xid);
+        }
+
+        try {
+            int status = importedTransaction.getStatus();
+            assert status == Status.STATUS_ACTIVE || status == Status.STATUS_PREPARED: "invalid status: " + status;
+        } catch (SystemException e) {
+            throw (XAException)new XAException().initCause(e);
+        }
+        commit(importedTransaction, onePhase);
+    }
+
+    /**
+     * @see javax.resource.spi.XATerminator#forget(javax.transaction.xa.Xid)
+     */
+    public void forget(Xid xid) throws XAException {
+        Transaction importedTransaction;
+        synchronized (importedTransactions) {
+            importedTransaction = (Transaction) importedTransactions.remove(xid);
+        }
+        if (importedTransaction == null) {
+            throw new XAException("No imported transaction for xid: " + xid);
+        }
+        //todo is there a correct status test here?
+//        try {
+//            int status = tx.getStatus();
+//            assert status == Status.STATUS_ACTIVE || status == Status.STATUS_PREPARED;
+//        } catch (SystemException e) {
+//            throw new XAException();
+//        }
+        forget(importedTransaction);
+    }
+
+    /**
+     * @see javax.resource.spi.XATerminator#prepare(javax.transaction.xa.Xid)
+     */
+    public int prepare(Xid xid) throws XAException {
+        Transaction importedTransaction;
+        synchronized (importedTransactions) {
+            importedTransaction = (Transaction) importedTransactions.get(xid);
+        }
+        if (importedTransaction == null) {
+            throw new XAException("No imported transaction for xid: " + xid);
+        }
+        try {
+            int status = importedTransaction.getStatus();
+            assert status == Status.STATUS_ACTIVE;
+        } catch (SystemException e) {
+            throw (XAException)new XAException().initCause(e);
+        }
+        return prepare(importedTransaction);
+    }
+
+    /**
+     * @see javax.resource.spi.XATerminator#recover(int)
+     */
+    public Xid[] recover(int flag) throws XAException {
+        if (!isInRecovery) {
+            if ((flag & XAResource.TMSTARTRSCAN) == 0) {
+                throw new XAException(XAException.XAER_PROTO);
+            }
+            isInRecovery = true;
+        }
+        if ((flag & XAResource.TMENDRSCAN) != 0) {
+            isInRecovery = false;
+        }
+        //we always return all xids in first call.
+        //calling "startrscan" repeatedly starts at beginning of list again.
+        if ((flag & XAResource.TMSTARTRSCAN) != 0) {
+            Map recoveredXidMap = getExternalXids();
+            Xid[] recoveredXids = new Xid[recoveredXidMap.size()];
+            int i = 0;
+            synchronized (importedTransactions) {
+                for (Iterator iterator = recoveredXidMap.entrySet().iterator(); iterator.hasNext();) {
+                    Map.Entry entry = (Map.Entry) iterator.next();
+                    Xid xid = (Xid) entry.getKey();
+                    recoveredXids[i++] = xid;
+                    Transaction transaction = (Transaction) entry.getValue();
+                    importedTransactions.put(xid, transaction);
+                }
+            }
+            return recoveredXids;
+        } else {
+            return new Xid[0];
+        }
+    }
+
+    /**
+     * @see javax.resource.spi.XATerminator#rollback(javax.transaction.xa.Xid)
+     */
+    public void rollback(Xid xid) throws XAException {
+        Transaction importedTransaction;
+        synchronized (importedTransactions) {
+            importedTransaction = (Transaction) importedTransactions.remove(xid);
+        }
+        if (importedTransaction == null) {
+            throw new XAException("No imported transaction for xid: " + xid);
+        }
+        try {
+            int status = importedTransaction.getStatus();
+            assert status == Status.STATUS_ACTIVE || status == Status.STATUS_PREPARED;
+        } catch (SystemException e) {
+            throw (XAException)new XAException().initCause(e);
+        }
+        rollback(importedTransaction);
+    }
+
+
+    //XAWork implementation
+    public void begin(Xid xid, long txTimeoutMillis) throws XAException, InvalidTransactionException, SystemException, ImportedTransactionActiveException {
+        Transaction importedTransaction;
+        synchronized (importedTransactions) {
+            importedTransaction = (Transaction) importedTransactions.get(xid);
+            if (importedTransaction == null) {
+                // this does not associate tx with current thread.
+                importedTransaction = importXid(xid, txTimeoutMillis);
+                importedTransactions.put(xid, importedTransaction);
+            }
+            // associate the the imported transaction with the current thread
+            try {
+                resume(importedTransaction);
+            } catch (InvalidTransactionException e) {
+                // this occures if our transaciton is associated with another thread
+                throw (ImportedTransactionActiveException)new ImportedTransactionActiveException(xid).initCause(e);
+            }
+        }
+    }
+
+    public void end(Xid xid) throws XAException, SystemException {
+        synchronized (importedTransactions) {
+            Transaction importedTransaction = (Transaction) importedTransactions.get(xid);
+            if (importedTransaction == null) {
+                throw new XAException("No imported transaction for xid: " + xid);
+            }
+            if (importedTransaction != getTransaction()) {
+                throw new XAException("Imported transaction is not associated with the curren thread xid: " + xid);
+            }
+            suspend();
+        }
+    }
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/ImportedTransactionActiveException.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/ImportedTransactionActiveException.java
new file mode 100644
index 0000000..7003048
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/ImportedTransactionActiveException.java
@@ -0,0 +1,36 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import javax.transaction.xa.Xid;
+
+/**
+ */
+public class ImportedTransactionActiveException extends Exception {
+
+    private final Xid xid;
+
+    public ImportedTransactionActiveException(Xid xid) {
+        this.xid = xid;
+    }
+
+    public Xid getXid() {
+        return xid;
+    }
+
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/LogException.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/LogException.java
new file mode 100644
index 0000000..94a8997
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/LogException.java
@@ -0,0 +1,43 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class LogException extends Exception {
+
+       public LogException() {
+	super();
+    }
+
+    public LogException(String message) {
+	super(message);
+    }
+
+    public LogException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public LogException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/MonitorableTransactionManager.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/MonitorableTransactionManager.java
new file mode 100644
index 0000000..397a416
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/MonitorableTransactionManager.java
@@ -0,0 +1,28 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.transaction.manager;
+
+import java.util.EventListener;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public interface MonitorableTransactionManager extends EventListener {
+    // todo add notifications for begin, syspend, resume, commit, rollback and exceptions
+    void addTransactionAssociationListener(TransactionManagerMonitor listener);
+    void removeTransactionAssociationListener(TransactionManagerMonitor listener);
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/NamedXAResource.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/NamedXAResource.java
new file mode 100644
index 0000000..47742dd
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/NamedXAResource.java
@@ -0,0 +1,32 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import javax.transaction.xa.XAResource;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public interface NamedXAResource extends XAResource {
+
+    String getName();
+
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/RecoverableTransactionManager.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/RecoverableTransactionManager.java
new file mode 100644
index 0000000..61c38f5
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/RecoverableTransactionManager.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+
+package org.apache.geronimo.transaction.manager;
+
+import javax.transaction.TransactionManager;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public interface RecoverableTransactionManager extends TransactionManager {
+    void recoveryError(Exception e);
+
+    void recoverResourceManager(NamedXAResource xaResource);
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/Recovery.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/Recovery.java
new file mode 100644
index 0000000..38cbed0
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/Recovery.java
@@ -0,0 +1,82 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Map;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.Xid;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public interface Recovery {
+
+    void recoverLog() throws XAException;
+
+    void recoverResourceManager(NamedXAResource xaResource) throws XAException;
+
+    boolean hasRecoveryErrors();
+
+    List getRecoveryErrors();
+
+    boolean localRecoveryComplete();
+
+    int localUnrecoveredCount();
+
+    //hard to implement.. needs ExternalTransaction to have a reference to externalXids.
+//    boolean remoteRecoveryComplete();
+
+    Map getExternalXids();
+
+    public static class XidBranchesPair {
+        private final Xid xid;
+
+        //set of TransactionBranchInfo
+        private final Set branches = new HashSet();
+
+        private final Object mark;
+
+        public XidBranchesPair(Xid xid, Object mark) {
+            this.xid = xid;
+            this.mark = mark;
+        }
+
+        public Xid getXid() {
+            return xid;
+        }
+
+        public Set getBranches() {
+            return branches;
+        }
+
+        public Object getMark() {
+            return mark;
+        }
+
+        public void addBranch(TransactionBranchInfo branchInfo) {
+            branches.add(branchInfo);
+        }
+    }
+
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/RecoveryImpl.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/RecoveryImpl.java
new file mode 100644
index 0000000..01a5520
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/RecoveryImpl.java
@@ -0,0 +1,264 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Collection;
+
+import javax.transaction.SystemException;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class RecoveryImpl implements Recovery {
+    private static final Log log = LogFactory.getLog("Recovery");
+
+    private final TransactionLog txLog;
+    private final XidFactory xidFactory;
+
+    private final Map externalXids = new HashMap();
+    private final Map ourXids = new HashMap();
+    private final Map nameToOurTxMap = new HashMap();
+    private final Map externalGlobalIdMap = new HashMap();
+
+    private final List recoveryErrors = new ArrayList();
+
+    public RecoveryImpl(final TransactionLog txLog, final XidFactory xidFactory) {
+        this.txLog = txLog;
+        this.xidFactory = xidFactory;
+    }
+
+    public synchronized void recoverLog() throws XAException {
+        Collection preparedXids = null;
+        try {
+            preparedXids = txLog.recover(xidFactory);
+        } catch (LogException e) {
+            throw (XAException) new XAException(XAException.XAER_RMERR).initCause(e);
+        }
+        for (Iterator iterator = preparedXids.iterator(); iterator.hasNext();) {
+            XidBranchesPair xidBranchesPair = (Recovery.XidBranchesPair) iterator.next();
+            Xid xid = xidBranchesPair.getXid();
+            if (xidFactory.matchesGlobalId(xid.getGlobalTransactionId())) {
+                ourXids.put(new ByteArrayWrapper(xid.getGlobalTransactionId()), xidBranchesPair);
+                for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) {
+                    String name = ((TransactionBranchInfo) branches.next()).getResourceName();
+                    Set transactionsForName = (Set)nameToOurTxMap.get(name);
+                    if (transactionsForName == null) {
+                        transactionsForName = new HashSet();
+                        nameToOurTxMap.put(name, transactionsForName);
+                    }
+                    transactionsForName.add(xidBranchesPair);
+                }
+            } else {
+                TransactionImpl externalTx = new ExternalTransaction(xid, txLog, xidBranchesPair.getBranches());
+                externalXids.put(xid, externalTx);
+                externalGlobalIdMap.put(xid.getGlobalTransactionId(), externalTx);
+            }
+        }
+    }
+
+
+    public synchronized void recoverResourceManager(NamedXAResource xaResource) throws XAException {
+        String name = xaResource.getName();
+        Xid[] prepared = xaResource.recover(XAResource.TMSTARTRSCAN + XAResource.TMENDRSCAN);
+        for (int i = 0; prepared != null && i < prepared.length; i++) {
+            Xid xid = prepared[i];
+            ByteArrayWrapper globalIdWrapper = new ByteArrayWrapper(xid.getGlobalTransactionId());
+            XidBranchesPair xidNamesPair = (XidBranchesPair) ourXids.get(globalIdWrapper);
+            
+            if (xidNamesPair != null) {
+                
+                // Only commit if this NamedXAResource was the XAResource for the transaction.
+                // Otherwise, wait for recoverResourceManager to be called for the actual XAResource 
+                // This is a bit wasteful, but given our management of XAResources by "name", is about the best we can do.
+                if (isNameInTransaction(xidNamesPair, name)) {
+                    try {
+                        xaResource.commit(xid, false);
+                    } catch(XAException e) {
+                        recoveryErrors.add(e);
+                        log.error(e);
+                    }
+                    removeNameFromTransaction(xidNamesPair, name, true);
+                }
+            } else if (xidFactory.matchesGlobalId(xid.getGlobalTransactionId())) {
+                //ours, but prepare not logged
+                try {
+                    xaResource.rollback(xid);
+                } catch (XAException e) {
+                    recoveryErrors.add(e);
+                    log.error(e);
+                }
+            } else if (xidFactory.matchesBranchId(xid.getBranchQualifier())) {
+                //our branch, but we did not start this tx.
+                TransactionImpl externalTx = (TransactionImpl) externalGlobalIdMap.get(xid.getGlobalTransactionId());
+                if (externalTx == null) {
+                    //we did not prepare this branch, rollback.
+                    try {
+                        xaResource.rollback(xid);
+                    } catch (XAException e) {
+                        recoveryErrors.add(e);
+                        log.error(e);
+                    }
+                } else {
+                    //we prepared this branch, must wait for commit/rollback command.
+                    externalTx.addBranchXid(xaResource, xid);
+                }
+            }
+            //else we had nothing to do with this xid.
+        }
+        Set transactionsForName = (Set)nameToOurTxMap.get(name);
+        if (transactionsForName != null) {
+            for (Iterator transactions = transactionsForName.iterator(); transactions.hasNext();) {
+                XidBranchesPair xidBranchesPair = (XidBranchesPair) transactions.next();
+                removeNameFromTransaction(xidBranchesPair, name, false);
+            }
+        }
+    }
+
+    private boolean isNameInTransaction(XidBranchesPair xidBranchesPair, String name) {
+        for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) {
+            TransactionBranchInfo transactionBranchInfo = (TransactionBranchInfo) branches.next();
+            if (name.equals(transactionBranchInfo.getResourceName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    private void removeNameFromTransaction(XidBranchesPair xidBranchesPair, String name, boolean warn) {
+        int removed = 0;
+        for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) {
+            TransactionBranchInfo transactionBranchInfo = (TransactionBranchInfo) branches.next();
+            if (name.equals(transactionBranchInfo.getResourceName())) {
+                branches.remove();
+                removed++;
+            }
+        }
+        if (warn && removed == 0) {
+            log.error("XAResource named: " + name + " returned branch xid for xid: " + xidBranchesPair.getXid() + " but was not registered with that transaction!");
+        }
+        if (xidBranchesPair.getBranches().isEmpty() && 0 != removed ) {
+            try {
+                ourXids.remove(new ByteArrayWrapper(xidBranchesPair.getXid().getGlobalTransactionId()));
+                txLog.commit(xidBranchesPair.getXid(), xidBranchesPair.getMark());
+            } catch (LogException e) {
+                recoveryErrors.add(e);
+                log.error(e);
+            }
+        }
+    }
+
+    public synchronized boolean hasRecoveryErrors() {
+        return !recoveryErrors.isEmpty();
+    }
+
+    public synchronized List getRecoveryErrors() {
+        return Collections.unmodifiableList(recoveryErrors);
+    }
+
+    public synchronized boolean localRecoveryComplete() {
+        return ourXids.isEmpty();
+    }
+
+    public synchronized int localUnrecoveredCount() {
+        return ourXids.size();
+    }
+
+    //hard to implement.. needs ExternalTransaction to have a reference to externalXids.
+//    public boolean remoteRecoveryComplete() {
+//    }
+
+    public synchronized Map getExternalXids() {
+        return new HashMap(externalXids);
+    }
+
+    private static class ByteArrayWrapper {
+        private final byte[] bytes;
+        private final int hashCode;
+
+        public ByteArrayWrapper(final byte[] bytes) {
+            assert bytes != null;
+            this.bytes = bytes;
+            int hash = 0;
+            for (int i = 0; i < bytes.length; i++) {
+                hash += 37 * bytes[i];
+            }
+            hashCode = hash;
+        }
+
+        public boolean equals(Object other) {
+            if (other instanceof ByteArrayWrapper) {
+                return Arrays.equals(bytes, ((ByteArrayWrapper)other).bytes);
+            }
+            return false;
+        }
+
+        public int hashCode() {
+            return hashCode;
+        }
+    }
+
+    private static class ExternalTransaction extends TransactionImpl {
+        private Set resourceNames;
+
+        public ExternalTransaction(Xid xid, TransactionLog txLog, Set resourceNames) {
+            super(xid, txLog);
+            this.resourceNames = resourceNames;
+        }
+
+        public boolean hasName(String name) {
+            return resourceNames.contains(name);
+        }
+
+        public void removeName(String name) {
+            resourceNames.remove(name);
+        }
+
+        public void preparedCommit() throws SystemException {
+            if (!resourceNames.isEmpty()) {
+                throw new SystemException("This tx does not have all resource managers online, commit not allowed yet");
+            }
+            super.preparedCommit();
+        }
+
+        public void rollback() throws SystemException {
+            if (!resourceNames.isEmpty()) {
+                throw new SystemException("This tx does not have all resource managers online, rollback not allowed yet");
+            }
+            super.rollback();
+
+        }
+    }
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionBranchInfo.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionBranchInfo.java
new file mode 100644
index 0000000..660e17d
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionBranchInfo.java
@@ -0,0 +1,34 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import javax.transaction.xa.Xid;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public interface TransactionBranchInfo {
+
+    String getResourceName();
+
+    Xid getBranchXid();
+
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionBranchInfoImpl.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionBranchInfoImpl.java
new file mode 100644
index 0000000..1aaf002
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionBranchInfoImpl.java
@@ -0,0 +1,46 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+
+import javax.transaction.xa.Xid;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class TransactionBranchInfoImpl implements TransactionBranchInfo {
+
+    private final Xid branchXid;
+    private final String resourceName;
+
+    public TransactionBranchInfoImpl(Xid branchXid, String resourceName) {
+        this.branchXid = branchXid;
+        this.resourceName = resourceName;
+    }
+
+    public Xid getBranchXid() {
+        return branchXid;
+    }
+
+    public String getResourceName() {
+        return resourceName;
+    }
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionImpl.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionImpl.java
new file mode 100644
index 0000000..d125743
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionImpl.java
@@ -0,0 +1,708 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Basic local transaction with support for multiple resources.
+ *
+ * @version $Rev$ $Date$
+ */
+public class TransactionImpl implements Transaction {
+    private static final Log log = LogFactory.getLog("Transaction");
+
+    private final XidFactory xidFactory;
+    private final Xid xid;
+    private final TransactionLog txnLog;
+    private final long timeout;
+    private final List syncList = new ArrayList(5);
+    private final List interposedSyncList = new ArrayList(3);
+    private final LinkedList resourceManagers = new LinkedList();
+    private final IdentityHashMap activeXaResources = new IdentityHashMap(3);
+    private final IdentityHashMap suspendedXaResources = new IdentityHashMap(3);
+    private int status = Status.STATUS_NO_TRANSACTION;
+    private Object logMark;
+
+    private final Map resources = new HashMap();
+
+    TransactionImpl(XidFactory xidFactory, TransactionLog txnLog, long transactionTimeoutMilliseconds) throws SystemException {
+        this(xidFactory.createXid(), xidFactory, txnLog, transactionTimeoutMilliseconds);
+    }
+
+    TransactionImpl(Xid xid, XidFactory xidFactory, TransactionLog txnLog, long transactionTimeoutMilliseconds) throws SystemException {
+        this.xidFactory = xidFactory;
+        this.txnLog = txnLog;
+        this.xid = xid;
+        this.timeout = transactionTimeoutMilliseconds + TransactionTimer.getCurrentTime();
+        try {
+            txnLog.begin(xid);
+        } catch (LogException e) {
+            status = Status.STATUS_MARKED_ROLLBACK;
+            SystemException ex = new SystemException("Error logging begin; transaction marked for roll back)");
+            ex.initCause(e);
+            throw ex;
+        }
+        status = Status.STATUS_ACTIVE;
+    }
+
+    //reconstruct a tx for an external tx found in recovery
+    public TransactionImpl(Xid xid, TransactionLog txLog) {
+        this.xidFactory = null;
+        this.txnLog = txLog;
+        this.xid = xid;
+        status = Status.STATUS_PREPARED;
+        //TODO is this a good idea?
+        this.timeout = Long.MAX_VALUE;
+    }
+
+    public synchronized int getStatus() {
+        return status;
+    }
+
+    public Object getResource(Object key) {
+        return resources.get(key);
+    }
+
+    public boolean getRollbackOnly() {
+        return status == Status.STATUS_MARKED_ROLLBACK;
+    }
+
+    public Object getTransactionKey() {
+        return xid;
+    }
+
+    public int getTransactionStatus() {
+        return status;
+    }
+
+    public void putResource(Object key, Object value) {
+        if (key == null) {
+            throw new NullPointerException("You must supply a non-null key for putResource");
+        }
+        resources.put(key, value);
+    }
+
+    public void registerInterposedSynchronization(Synchronization synchronization) {
+        interposedSyncList.add(synchronization);
+    }
+
+    public synchronized void setRollbackOnly() throws IllegalStateException {
+        switch (status) {
+            case Status.STATUS_ACTIVE:
+            case Status.STATUS_PREPARING:
+                status = Status.STATUS_MARKED_ROLLBACK;
+                break;
+            case Status.STATUS_MARKED_ROLLBACK:
+            case Status.STATUS_ROLLING_BACK:
+                // nothing to do
+                break;
+            default:
+                throw new IllegalStateException("Cannot set rollback only, status is " + getStateString(status));
+        }
+    }
+
+    public synchronized void registerSynchronization(Synchronization synch) throws IllegalStateException, RollbackException, SystemException {
+        if (synch == null) {
+            throw new IllegalArgumentException("Synchronization is null");
+        }
+        switch (status) {
+            case Status.STATUS_ACTIVE:
+            case Status.STATUS_PREPARING:
+                break;
+            case Status.STATUS_MARKED_ROLLBACK:
+                throw new RollbackException("Transaction is marked for rollback");
+            default:
+                throw new IllegalStateException("Status is " + getStateString(status));
+        }
+        syncList.add(synch);
+    }
+
+    public synchronized boolean enlistResource(XAResource xaRes) throws IllegalStateException, RollbackException, SystemException {
+        if (xaRes == null) {
+            throw new IllegalArgumentException("XAResource is null");
+        }
+        switch (status) {
+            case Status.STATUS_ACTIVE:
+                break;
+            case Status.STATUS_MARKED_ROLLBACK:
+                break;
+            default:
+                throw new IllegalStateException("Status is " + getStateString(status));
+        }
+
+        if (activeXaResources.containsKey(xaRes)) {
+            throw new IllegalStateException("xaresource: " + xaRes + " is already enlisted!");
+        }
+
+        try {
+            TransactionBranch manager = (TransactionBranch) suspendedXaResources.remove(xaRes);
+            if (manager != null) {
+                //we know about this one, it was suspended
+                xaRes.start(manager.getBranchId(), XAResource.TMRESUME);
+                activeXaResources.put(xaRes, manager);
+                return true;
+            }
+            //it is not suspended.
+            for (Iterator i = resourceManagers.iterator(); i.hasNext();) {
+                manager = (TransactionBranch) i.next();
+                boolean sameRM;
+                //if the xares is already known, we must be resuming after a suspend.
+                if (xaRes == manager.getCommitter()) {
+                    throw new IllegalStateException("xaRes " + xaRes + " is a committer but is not active or suspended");
+                }
+                //Otherwise, see if this is a new xares for the same resource manager
+                try {
+                    sameRM = xaRes.isSameRM(manager.getCommitter());
+                } catch (XAException e) {
+                    log.warn("Unexpected error checking for same RM", e);
+                    continue;
+                }
+                if (sameRM) {
+                    xaRes.start(manager.getBranchId(), XAResource.TMJOIN);
+                    activeXaResources.put(xaRes, manager);
+                    return true;
+                }
+            }
+            //we know nothing about this XAResource or resource manager
+            Xid branchId = xidFactory.createBranch(xid, resourceManagers.size() + 1);
+            xaRes.start(branchId, XAResource.TMNOFLAGS);
+            activeXaResources.put(xaRes, addBranchXid(xaRes, branchId));
+            return true;
+        } catch (XAException e) {
+            log.warn("Unable to enlist XAResource " + xaRes + ", errorCode: " + e.errorCode, e);
+            return false;
+        }
+    }
+
+    public synchronized boolean delistResource(XAResource xaRes, int flag) throws IllegalStateException, SystemException {
+        if (!(flag == XAResource.TMFAIL || flag == XAResource.TMSUCCESS || flag == XAResource.TMSUSPEND)) {
+            throw new IllegalStateException("invalid flag for delistResource: " + flag);
+        }
+        if (xaRes == null) {
+            throw new IllegalArgumentException("XAResource is null");
+        }
+        switch (status) {
+            case Status.STATUS_ACTIVE:
+            case Status.STATUS_MARKED_ROLLBACK:
+                break;
+            default:
+                throw new IllegalStateException("Status is " + getStateString(status));
+        }
+        TransactionBranch manager = (TransactionBranch) activeXaResources.remove(xaRes);
+        if (manager == null) {
+            if (flag == XAResource.TMSUSPEND) {
+                throw new IllegalStateException("trying to suspend an inactive xaresource: " + xaRes);
+            }
+            //not active, and we are not trying to suspend.  We must be ending tx.
+            manager = (TransactionBranch) suspendedXaResources.remove(xaRes);
+            if (manager == null) {
+                throw new IllegalStateException("Resource not known to transaction: " + xaRes);
+            }
+        }
+
+        try {
+            xaRes.end(manager.getBranchId(), flag);
+            if (flag == XAResource.TMSUSPEND) {
+                suspendedXaResources.put(xaRes, manager);
+            }
+            return true;
+        } catch (XAException e) {
+            log.warn("Unable to delist XAResource " + xaRes + ", error code: " + e.errorCode, e);
+            return false;
+        }
+    }
+
+    //Transaction method, does 2pc
+    public void commit() throws HeuristicMixedException, HeuristicRollbackException, RollbackException, SecurityException, SystemException {
+        beforePrepare();
+
+        try {
+            boolean timedout = false;
+            if (TransactionTimer.getCurrentTime() > timeout) {
+                status = Status.STATUS_MARKED_ROLLBACK;
+                timedout = true;
+            }
+
+            if (status == Status.STATUS_MARKED_ROLLBACK) {
+                rollbackResources(resourceManagers);
+                if (timedout) {
+                    throw new RollbackException("Transaction timout");
+                } else {
+                    throw new RollbackException("Unable to commit: transaction marked for rollback");
+                }
+            }
+            synchronized (this) {
+                if (status == Status.STATUS_ACTIVE) {
+                    if (this.resourceManagers.size() == 0) {
+                        // nothing to commit
+                        status = Status.STATUS_COMMITTED;
+                    } else if (this.resourceManagers.size() == 1) {
+                        // one-phase commit decision
+                        status = Status.STATUS_COMMITTING;
+                    } else {
+                        // start prepare part of two-phase
+                        status = Status.STATUS_PREPARING;
+                    }
+                }
+                // resourceManagers is now immutable
+            }
+
+            // no-phase
+            if (resourceManagers.size() == 0) {
+                synchronized (this) {
+                    status = Status.STATUS_COMMITTED;
+                }
+                return;
+            }
+
+            // one-phase
+            if (resourceManagers.size() == 1) {
+                TransactionBranch manager = (TransactionBranch) resourceManagers.getFirst();
+                try {
+                    manager.getCommitter().commit(manager.getBranchId(), true);
+                    synchronized (this) {
+                        status = Status.STATUS_COMMITTED;
+                    }
+                    return;
+                } catch (XAException e) {
+                    synchronized (this) {
+                        status = Status.STATUS_ROLLEDBACK;
+                    }
+                    throw (RollbackException) new RollbackException("Error during one-phase commit").initCause(e);
+                }
+            }
+
+            // two-phase
+            boolean willCommit = internalPrepare();
+
+            // notify the RMs
+            if (willCommit) {
+                commitResources(resourceManagers);
+            } else {
+                rollbackResources(resourceManagers);
+                throw new RollbackException("Unable to commit");
+            }
+        } finally {
+            afterCompletion();
+            synchronized (this) {
+                status = Status.STATUS_NO_TRANSACTION;
+            }
+        }
+    }
+
+    //Used from XATerminator for first phase in a remotely controlled tx.
+    int prepare() throws SystemException, RollbackException {
+        beforePrepare();
+        int result = XAResource.XA_RDONLY;
+        try {
+            LinkedList rms;
+            synchronized (this) {
+                if (status == Status.STATUS_ACTIVE) {
+                    if (resourceManagers.size() == 0) {
+                        // nothing to commit
+                        status = Status.STATUS_COMMITTED;
+                        return result;
+                    } else {
+                        // start prepare part of two-phase
+                        status = Status.STATUS_PREPARING;
+                    }
+                }
+                // resourceManagers is now immutable
+                rms = resourceManagers;
+            }
+
+            boolean willCommit = internalPrepare();
+
+            // notify the RMs
+            if (willCommit) {
+                if (!rms.isEmpty()) {
+                    result = XAResource.XA_OK;
+                }
+            } else {
+                rollbackResources(rms);
+                throw new RollbackException("Unable to commit");
+            }
+        } finally {
+            if (result == XAResource.XA_RDONLY) {
+                afterCompletion();
+                synchronized (this) {
+                    status = Status.STATUS_NO_TRANSACTION;
+                }
+            }
+        }
+        return result;
+    }
+
+    //used from XATerminator for commit phase of non-readonly remotely controlled tx.
+    void preparedCommit() throws SystemException {
+        try {
+            commitResources(resourceManagers);
+        } finally {
+            afterCompletion();
+            synchronized (this) {
+                status = Status.STATUS_NO_TRANSACTION;
+            }
+        }
+    }
+
+    //helper method used by Transaction.commit and XATerminator prepare.
+    private void beforePrepare() {
+        synchronized (this) {
+            switch (status) {
+                case Status.STATUS_ACTIVE:
+                case Status.STATUS_MARKED_ROLLBACK:
+                    break;
+                default:
+                    throw new IllegalStateException("Status is " + getStateString(status));
+            }
+        }
+
+        beforeCompletion();
+        endResources();
+    }
+
+
+    //helper method used by Transaction.commit and XATerminator prepare.
+    private boolean internalPrepare() throws SystemException {
+
+        for (Iterator rms = resourceManagers.iterator(); rms.hasNext();) {
+            synchronized (this) {
+                if (status != Status.STATUS_PREPARING) {
+                    // we were marked for rollback
+                    break;
+                }
+            }
+            TransactionBranch manager = (TransactionBranch) rms.next();
+            try {
+                int vote = manager.getCommitter().prepare(manager.getBranchId());
+                if (vote == XAResource.XA_RDONLY) {
+                    // we don't need to consider this RM any more
+                    rms.remove();
+                }
+            } catch (XAException e) {
+                synchronized (this) {
+                    status = Status.STATUS_MARKED_ROLLBACK;
+                    //TODO document why this is true from the spec.
+                    //XAException during prepare means we can assume resource is rolled back.
+                    rms.remove();
+                    break;
+                }
+            }
+        }
+
+        // decision time...
+        boolean willCommit;
+        synchronized (this) {
+            willCommit = (status != Status.STATUS_MARKED_ROLLBACK);
+            if (willCommit) {
+                status = Status.STATUS_PREPARED;
+            }
+        }
+        // log our decision
+        if (willCommit && !resourceManagers.isEmpty()) {
+            try {
+                logMark = txnLog.prepare(xid, resourceManagers);
+            } catch (LogException e) {
+                try {
+                    rollbackResources(resourceManagers);
+                } catch (Exception se) {
+                    log.error("Unable to rollback after failure to log prepare", se.getCause());
+                }
+                throw (SystemException) new SystemException("Error logging prepare; transaction was rolled back)").initCause(e);
+            }
+        }
+        return willCommit;
+    }
+
+    public void rollback() throws IllegalStateException, SystemException {
+        List rms;
+        synchronized (this) {
+            switch (status) {
+                case Status.STATUS_ACTIVE:
+                    status = Status.STATUS_MARKED_ROLLBACK;
+                    break;
+                case Status.STATUS_MARKED_ROLLBACK:
+                    break;
+                default:
+                    throw new IllegalStateException("Status is " + getStateString(status));
+            }
+            rms = resourceManagers;
+        }
+
+        beforeCompletion();
+        endResources();
+        try {
+            rollbackResources(rms);
+            //only write rollback record if we have already written prepare record.
+            if (logMark != null) {
+                try {
+                    txnLog.rollback(xid, logMark);
+                } catch (LogException e) {
+                    try {
+                        rollbackResources(rms);
+                    } catch (Exception se) {
+                        log.error("Unable to rollback after failure to log decision", se.getCause());
+                    }
+                    throw (SystemException) new SystemException("Error logging rollback").initCause(e);
+                }
+            }
+        } finally {
+            afterCompletion();
+            synchronized (this) {
+                status = Status.STATUS_NO_TRANSACTION;
+            }
+        }
+    }
+
+    private void beforeCompletion() {
+        beforeCompletion(syncList);
+        beforeCompletion(interposedSyncList);
+    }
+
+    private void beforeCompletion(List syncs) {
+        int i = 0;
+        while (true) {
+            Synchronization synch;
+            synchronized (this) {
+                if (i == syncs.size()) {
+                    return;
+                } else {
+                    synch = (Synchronization) syncs.get(i++);
+                }
+            }
+            try {
+                synch.beforeCompletion();
+            } catch (Exception e) {
+                log.warn("Unexpected exception from beforeCompletion; transaction will roll back", e);
+                synchronized (this) {
+                    status = Status.STATUS_MARKED_ROLLBACK;
+                }
+            }
+        }
+    }
+
+    private void afterCompletion() {
+        // this does not synchronize because nothing can modify our state at this time
+        afterCompletion(interposedSyncList);
+        afterCompletion(syncList);
+    }
+
+    private void afterCompletion(List syncs) {
+        for (Iterator i = syncs.iterator(); i.hasNext();) {
+            Synchronization synch = (Synchronization) i.next();
+            try {
+                synch.afterCompletion(status);
+            } catch (Exception e) {
+                log.warn("Unexpected exception from afterCompletion; continuing", e);
+            }
+        }
+    }
+
+    private void endResources() {
+        endResources(activeXaResources);
+        endResources(suspendedXaResources);
+    }
+
+    private void endResources(IdentityHashMap resourceMap) {
+        while (true) {
+            XAResource xaRes;
+            TransactionBranch manager;
+            int flags;
+            synchronized (this) {
+                Set entrySet = resourceMap.entrySet();
+                if (entrySet.isEmpty()) {
+                    return;
+                }
+                Map.Entry entry = (Map.Entry) entrySet.iterator().next();
+                xaRes = (XAResource) entry.getKey();
+                manager = (TransactionBranch) entry.getValue();
+                flags = (status == Status.STATUS_MARKED_ROLLBACK) ? XAResource.TMFAIL : XAResource.TMSUCCESS;
+                resourceMap.remove(xaRes);
+            }
+            try {
+                xaRes.end(manager.getBranchId(), flags);
+            } catch (XAException e) {
+                log.warn("Error ending association for XAResource " + xaRes + "; transaction will roll back. XA error code: " + e.errorCode, e);
+                synchronized (this) {
+                    status = Status.STATUS_MARKED_ROLLBACK;
+                }
+            }
+        }
+    }
+
+    private void rollbackResources(List rms) throws SystemException {
+        SystemException cause = null;
+        synchronized (this) {
+            status = Status.STATUS_ROLLING_BACK;
+        }
+        for (Iterator i = rms.iterator(); i.hasNext();) {
+            TransactionBranch manager = (TransactionBranch) i.next();
+            try {
+                manager.getCommitter().rollback(manager.getBranchId());
+            } catch (XAException e) {
+                log.error("Unexpected exception rolling back " + manager.getCommitter() + "; continuing with rollback", e);
+                if (cause == null) {
+                    cause = new SystemException(e.errorCode);
+                }
+            }
+        }
+        synchronized (this) {
+            status = Status.STATUS_ROLLEDBACK;
+        }
+        if (cause != null) {
+            throw cause;
+        }
+    }
+
+    private void commitResources(List rms) throws SystemException {
+        SystemException cause = null;
+        synchronized (this) {
+            status = Status.STATUS_COMMITTING;
+        }
+        for (Iterator i = rms.iterator(); i.hasNext();) {
+            TransactionBranch manager = (TransactionBranch) i.next();
+            try {
+                manager.getCommitter().commit(manager.getBranchId(), false);
+            } catch (XAException e) {
+                log.error("Unexpected exception committing" + manager.getCommitter() + "; continuing to commit other RMs", e);
+                if (cause == null) {
+                    cause = new SystemException(e.errorCode);
+                }
+            }
+        }
+        //if all resources were read only, we didn't write a prepare record.
+        if (!rms.isEmpty()) {
+            try {
+                txnLog.commit(xid, logMark);
+            } catch (LogException e) {
+                log.error("Unexpected exception logging commit completion for xid " + xid, e);
+                throw (SystemException) new SystemException("Unexpected error logging commit completion for xid " + xid).initCause(e);
+            }
+        }
+        synchronized (this) {
+            status = Status.STATUS_COMMITTED;
+        }
+        if (cause != null) {
+            throw cause;
+        }
+    }
+
+    private static String getStateString(int status) {
+        switch (status) {
+            case Status.STATUS_ACTIVE:
+                return "STATUS_ACTIVE";
+            case Status.STATUS_PREPARING:
+                return "STATUS_PREPARING";
+            case Status.STATUS_PREPARED:
+                return "STATUS_PREPARED";
+            case Status.STATUS_MARKED_ROLLBACK:
+                return "STATUS_MARKED_ROLLBACK";
+            case Status.STATUS_ROLLING_BACK:
+                return "STATUS_ROLLING_BACK";
+            case Status.STATUS_COMMITTING:
+                return "STATUS_COMMITTING";
+            case Status.STATUS_COMMITTED:
+                return "STATUS_COMMITTED";
+            case Status.STATUS_ROLLEDBACK:
+                return "STATUS_ROLLEDBACK";
+            case Status.STATUS_NO_TRANSACTION:
+                return "STATUS_NO_TRANSACTION";
+            case Status.STATUS_UNKNOWN:
+                return "STATUS_UNKNOWN";
+            default:
+                throw new AssertionError();
+        }
+    }
+
+    public boolean equals(Object obj) {
+        if (obj instanceof TransactionImpl) {
+            TransactionImpl other = (TransactionImpl) obj;
+            return xid.equals(other.xid);
+        } else {
+            return false;
+        }
+    }
+
+    //when used from recovery, do not add manager to active or suspended resource maps.
+    // The xaresources have already been ended with TMSUCCESS.
+    public TransactionBranch addBranchXid(XAResource xaRes, Xid branchId) {
+        TransactionBranch manager = new TransactionBranch(xaRes, branchId);
+        resourceManagers.add(manager);
+        return manager;
+    }
+
+    private static class TransactionBranch implements TransactionBranchInfo {
+        private final XAResource committer;
+        private final Xid branchId;
+
+        public TransactionBranch(XAResource xaRes, Xid branchId) {
+            committer = xaRes;
+            this.branchId = branchId;
+        }
+
+        public XAResource getCommitter() {
+            return committer;
+        }
+
+        public Xid getBranchId() {
+            return branchId;
+        }
+
+        public String getResourceName() {
+            if (committer instanceof NamedXAResource) {
+                return ((NamedXAResource) committer).getName();
+            } else {
+                // if it isn't a named resource should we really stop all processing here!
+                // Maybe this would be better to handle else where and do we really want to prevent all processing of transactions?
+                log.error("Please correct the integration and supply a NamedXAResource", new IllegalStateException("Cannot log transactions as " + committer + " is not a NamedXAResource."));
+                return committer.toString();
+            }
+        }
+
+        public Xid getBranchXid() {
+            return branchId;
+        }
+    }
+
+
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionLog.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionLog.java
new file mode 100644
index 0000000..2a56f9b
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionLog.java
@@ -0,0 +1,62 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.transaction.xa.Xid;
+
+
+/**
+ * Interface used to notify a logging subsystem of transaction events.
+ *
+ * @version $Rev$ $Date$
+ */
+public interface TransactionLog {
+
+    void begin(Xid xid) throws LogException;
+
+    /**
+     * log prepare for the global xid xid and the list of TransactionBranchInfo branches
+     * @param xid global xid for the transactions
+     * @param branches List of TransactionBranchInfo
+     * @throws LogException
+     */
+    Object prepare(Xid xid, List branches) throws LogException;
+
+    void commit(Xid xid, Object logMark) throws LogException;
+
+    void rollback(Xid xid, Object logMark) throws LogException;
+
+    /**
+     * Recovers the log, returning a map of (top level) xid to List of TransactionBranchInfo for the branches.
+     * Uses the XidFactory to reconstruct the xids.
+     *
+     * @param xidFactory
+     * @return Map of recovered xid to List of TransactionBranchInfo representing the branches.
+     * @throws LogException
+     */
+    Collection recover(XidFactory xidFactory) throws LogException;
+
+    String getXMLStats();
+
+    int getAverageForceTime();
+
+    int getAverageBytesPerForce();
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionManagerImpl.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionManagerImpl.java
new file mode 100644
index 0000000..c99a9ef
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionManagerImpl.java
@@ -0,0 +1,371 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.transaction.*;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.Xid;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.geronimo.transaction.log.UnrecoverableLog;
+
+/**
+ * Simple implementation of a transaction manager.
+ *
+ * @version $Rev$ $Date$
+ */
+public class TransactionManagerImpl implements TransactionManager, UserTransaction, TransactionSynchronizationRegistry, XidImporter, MonitorableTransactionManager, RecoverableTransactionManager {
+    private static final Log log = LogFactory.getLog(TransactionManagerImpl.class);
+    protected static final int DEFAULT_TIMEOUT = 600;
+    protected static final byte[] DEFAULT_TM_ID = new byte[] {71,84,77,73,68};
+
+    final TransactionLog transactionLog;
+    final XidFactory xidFactory;
+    private final int defaultTransactionTimeoutMilliseconds;
+    private final ThreadLocal transactionTimeoutMilliseconds = new ThreadLocal();
+    private final ThreadLocal threadTx = new ThreadLocal();
+    private final ConcurrentHashMap associatedTransactions = new ConcurrentHashMap();
+    private static final Log recoveryLog = LogFactory.getLog("RecoveryController");
+    final Recovery recovery;
+    private final CopyOnWriteArrayList transactionAssociationListeners = new CopyOnWriteArrayList();
+    private List recoveryErrors = new ArrayList();
+
+    public TransactionManagerImpl() throws XAException {
+        this(DEFAULT_TIMEOUT,
+                null,
+                null
+        );
+    }
+
+    public TransactionManagerImpl(int defaultTransactionTimeoutSeconds) throws XAException {
+        this(defaultTransactionTimeoutSeconds,
+                null,
+                null
+        );
+    }
+
+    public TransactionManagerImpl(int defaultTransactionTimeoutSeconds, TransactionLog transactionLog) throws XAException {
+        this(defaultTransactionTimeoutSeconds,
+                null,
+                transactionLog
+        );
+    }
+
+    public TransactionManagerImpl(int defaultTransactionTimeoutSeconds, XidFactory xidFactory, TransactionLog transactionLog) throws XAException {
+        if (defaultTransactionTimeoutSeconds <= 0) {
+            throw new IllegalArgumentException("defaultTransactionTimeoutSeconds must be positive: attempted value: " + defaultTransactionTimeoutSeconds);
+        }
+
+        this.defaultTransactionTimeoutMilliseconds = defaultTransactionTimeoutSeconds * 1000;
+
+        if (transactionLog == null) {
+            this.transactionLog = new UnrecoverableLog();
+        } else {
+            this.transactionLog = transactionLog;
+        }
+
+        if (xidFactory != null) {
+            this.xidFactory = xidFactory;
+        } else {
+            this.xidFactory = new XidFactoryImpl(DEFAULT_TM_ID);
+        }
+
+        recovery = new RecoveryImpl(this.transactionLog, this.xidFactory);
+    }
+
+    public Transaction getTransaction() {
+        return (Transaction) threadTx.get();
+    }
+
+    private void associate(TransactionImpl tx) throws InvalidTransactionException {
+        if (tx == null) throw new NullPointerException("tx is null");
+
+        Object existingAssociation = associatedTransactions.putIfAbsent(tx, Thread.currentThread());
+        if (existingAssociation != null) {
+            throw new InvalidTransactionException("Specified transaction is already associated with another thread");
+        }
+        threadTx.set(tx);
+        fireThreadAssociated(tx);
+    }
+
+    private void unassociate() {
+        Transaction tx = getTransaction();
+        if (tx != null) {
+            associatedTransactions.remove(tx);
+            threadTx.set(null);
+            fireThreadUnassociated(tx);
+        }
+    }
+
+    public void setTransactionTimeout(int seconds) throws SystemException {
+        if (seconds < 0) {
+            throw new SystemException("transaction timeout must be positive or 0 to reset to default");
+        }
+        if (seconds == 0) {
+            transactionTimeoutMilliseconds.set(null);
+        } else {
+            transactionTimeoutMilliseconds.set(new Long(seconds * 1000));
+        }
+    }
+
+    public int getStatus() throws SystemException {
+        Transaction tx = getTransaction();
+        return (tx != null) ? tx.getStatus() : Status.STATUS_NO_TRANSACTION;
+    }
+
+    public void begin() throws NotSupportedException, SystemException {
+        begin(getTransactionTimeoutMilliseconds(0L));
+    }
+
+    public Transaction begin(long transactionTimeoutMilliseconds) throws NotSupportedException, SystemException {
+        if (getStatus() != Status.STATUS_NO_TRANSACTION) {
+            throw new NotSupportedException("Nested Transactions are not supported");
+        }
+        TransactionImpl tx = new TransactionImpl(xidFactory, transactionLog, getTransactionTimeoutMilliseconds(transactionTimeoutMilliseconds));
+//        timeoutTimer.schedule(tx, getTransactionTimeoutMilliseconds(transactionTimeoutMilliseconds));
+        try {
+            associate(tx);
+        } catch (InvalidTransactionException e) {
+            // should not be possible since we just created that transaction and no one has a reference yet
+            throw (SystemException)new SystemException("Internal error: associate threw an InvalidTransactionException for a newly created transaction").initCause(e);
+        }
+        // Todo: Verify if this is correct thing to do. Use default timeout for next transaction.
+        this.transactionTimeoutMilliseconds.set(null);
+        return tx;
+    }
+
+    public Transaction suspend() throws SystemException {
+        Transaction tx = getTransaction();
+        if (tx != null) {
+            unassociate();
+        }
+        return tx;
+    }
+
+    public void resume(Transaction tx) throws IllegalStateException, InvalidTransactionException, SystemException {
+        if (getTransaction() != null) {
+            throw new IllegalStateException("Thread already associated with another transaction");
+        }
+        if (!(tx instanceof TransactionImpl)) {
+            throw new InvalidTransactionException("Cannot resume foreign transaction: " + tx);
+        }
+        associate((TransactionImpl) tx);
+    }
+
+    public Object getResource(Object key) {
+        TransactionImpl tx = getActiveTransactionImpl();
+        return tx.getResource(key);
+    }
+
+    private TransactionImpl getActiveTransactionImpl() {
+        TransactionImpl tx = (TransactionImpl)threadTx.get();
+        if (tx == null) {
+            throw new IllegalStateException("No tx on thread");
+        }
+        if (tx.getStatus() != Status.STATUS_ACTIVE && tx.getStatus() != Status.STATUS_MARKED_ROLLBACK) {
+            throw new IllegalStateException("Transaction " + tx + " is not active");
+        }
+        return tx;
+    }
+
+    public boolean getRollbackOnly() {
+        TransactionImpl tx = getActiveTransactionImpl();
+        return tx.getRollbackOnly();
+    }
+
+    public Object getTransactionKey() {
+        TransactionImpl tx = getActiveTransactionImpl();
+        return tx.getTransactionKey();
+    }
+
+    public int getTransactionStatus() {
+        TransactionImpl tx = (TransactionImpl) getTransaction();
+        return tx == null? Status.STATUS_NO_TRANSACTION: tx.getTransactionStatus();
+    }
+
+    public void putResource(Object key, Object value) {
+        TransactionImpl tx = getActiveTransactionImpl();
+        tx.putResource(key, value);
+    }
+
+    /**
+     * jta 1.1 method so the jpa implementations can be told to flush their caches.
+     * @param synchronization
+     */
+    public void registerInterposedSynchronization(Synchronization synchronization) {
+        TransactionImpl tx = getActiveTransactionImpl();
+        tx.registerInterposedSynchronization(synchronization);
+    }
+
+    public void setRollbackOnly() throws IllegalStateException {
+        TransactionImpl tx = (TransactionImpl) threadTx.get();
+        if (tx == null) {
+            throw new IllegalStateException("No transaction associated with current thread");
+        }
+        tx.setRollbackOnly();
+    }
+
+    public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException, RollbackException, SecurityException, SystemException {
+        Transaction tx = getTransaction();
+        if (tx == null) {
+            throw new IllegalStateException("No transaction associated with current thread");
+        }
+        try {
+            tx.commit();
+        } finally {
+            unassociate();
+        }
+    }
+
+    public void rollback() throws IllegalStateException, SecurityException, SystemException {
+        Transaction tx = getTransaction();
+        if (tx == null) {
+            throw new IllegalStateException("No transaction associated with current thread");
+        }
+        try {
+            tx.rollback();
+        } finally {
+            unassociate();
+        }
+    }
+
+    //XidImporter implementation
+    public Transaction importXid(Xid xid, long transactionTimeoutMilliseconds) throws XAException, SystemException {
+        if (transactionTimeoutMilliseconds < 0) {
+            throw new SystemException("transaction timeout must be positive or 0 to reset to default");
+        }
+        TransactionImpl tx = new TransactionImpl(xid, xidFactory, transactionLog, getTransactionTimeoutMilliseconds(transactionTimeoutMilliseconds));
+        return tx;
+    }
+
+    public void commit(Transaction tx, boolean onePhase) throws XAException {
+        if (onePhase) {
+            try {
+                tx.commit();
+            } catch (HeuristicMixedException e) {
+                throw (XAException) new XAException().initCause(e);
+            } catch (HeuristicRollbackException e) {
+                throw (XAException) new XAException().initCause(e);
+            } catch (RollbackException e) {
+                throw (XAException) new XAException().initCause(e);
+            } catch (SecurityException e) {
+                throw (XAException) new XAException().initCause(e);
+            } catch (SystemException e) {
+                throw (XAException) new XAException().initCause(e);
+            }
+        } else {
+            try {
+                ((TransactionImpl) tx).preparedCommit();
+            } catch (SystemException e) {
+                throw (XAException) new XAException().initCause(e);
+            }
+        }
+    }
+
+    public void forget(Transaction tx) throws XAException {
+        //TODO implement this!
+    }
+
+    public int prepare(Transaction tx) throws XAException {
+        try {
+            return ((TransactionImpl) tx).prepare();
+        } catch (SystemException e) {
+            throw (XAException) new XAException().initCause(e);
+        } catch (RollbackException e) {
+            throw (XAException) new XAException().initCause(e);
+        }
+    }
+
+    public void rollback(Transaction tx) throws XAException {
+        try {
+            tx.rollback();
+        } catch (IllegalStateException e) {
+            throw (XAException) new XAException().initCause(e);
+        } catch (SystemException e) {
+            throw (XAException) new XAException().initCause(e);
+        }
+    }
+
+    long getTransactionTimeoutMilliseconds(long transactionTimeoutMilliseconds) {
+        if (transactionTimeoutMilliseconds != 0) {
+            return transactionTimeoutMilliseconds;
+        }
+        Long timeout = (Long) this.transactionTimeoutMilliseconds.get();
+        if (timeout != null) {
+            return timeout.longValue();
+        }
+        return defaultTransactionTimeoutMilliseconds;
+    }
+
+    //Recovery
+    public void recoveryError(Exception e) {
+        recoveryLog.error(e);
+        recoveryErrors.add(e);
+    }
+
+    public void recoverResourceManager(NamedXAResource xaResource) {
+        try {
+            recovery.recoverResourceManager(xaResource);
+        } catch (XAException e) {
+            recoveryError(e);
+        }
+    }
+
+    public Map getExternalXids() {
+        return new HashMap(recovery.getExternalXids());
+    }
+
+    public void addTransactionAssociationListener(TransactionManagerMonitor listener) {
+        transactionAssociationListeners.addIfAbsent(listener);
+    }
+
+    public void removeTransactionAssociationListener(TransactionManagerMonitor listener) {
+        transactionAssociationListeners.remove(listener);
+    }
+
+    protected void fireThreadAssociated(Transaction tx) {
+        for (Iterator iterator = transactionAssociationListeners.iterator(); iterator.hasNext();) {
+            TransactionManagerMonitor listener = (TransactionManagerMonitor) iterator.next();
+            try {
+                listener.threadAssociated(tx);
+            } catch (Exception e) {
+                log.warn("Error calling transaction association listener", e);
+            }
+        }
+    }
+
+    protected void fireThreadUnassociated(Transaction tx) {
+        for (Iterator iterator = transactionAssociationListeners.iterator(); iterator.hasNext();) {
+            TransactionManagerMonitor listener = (TransactionManagerMonitor) iterator.next();
+            try {
+                listener.threadUnassociated(tx);
+            } catch (Exception e) {
+                log.warn("Error calling transaction association listener", e);
+            }
+        }
+    }
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionManagerMonitor.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionManagerMonitor.java
new file mode 100644
index 0000000..260a344
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionManagerMonitor.java
@@ -0,0 +1,28 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+package org.apache.geronimo.transaction.manager;
+
+import java.util.EventListener;
+import javax.transaction.Transaction;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public interface TransactionManagerMonitor extends EventListener {
+    void threadAssociated(Transaction transaction);
+    void threadUnassociated(Transaction transaction);
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionTimer.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionTimer.java
new file mode 100644
index 0000000..d006e12
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/TransactionTimer.java
@@ -0,0 +1,56 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+/**
+ * TODO improve shutdown
+ *
+ * @version $Revision$ $Date$
+ */
+public class TransactionTimer {
+    private static volatile long currentTime;
+
+    private static class CurrentTime extends Thread {
+        protected CurrentTime() {
+            currentTime = System.currentTimeMillis();
+            setContextClassLoader(null);
+        }
+
+        public void run() {
+            for (; ;) {
+                currentTime = System.currentTimeMillis();
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    // Ignore exception
+                }
+            }
+        }
+    }
+
+    static {
+        CurrentTime tm = new CurrentTime();
+        tm.setDaemon(true);
+        tm.start();
+    }
+
+    public static long getCurrentTime() {
+        return currentTime;
+    }
+
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/WrapperNamedXAResource.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/WrapperNamedXAResource.java
new file mode 100644
index 0000000..85e4129
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/WrapperNamedXAResource.java
@@ -0,0 +1,90 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import javax.transaction.xa.XAException;
+
+import org.apache.geronimo.transaction.manager.NamedXAResource;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class WrapperNamedXAResource implements NamedXAResource {
+
+    private final XAResource xaResource;
+    private final String name;
+
+    public WrapperNamedXAResource(XAResource xaResource, String name) {
+        this.xaResource = xaResource;
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void commit(Xid xid, boolean onePhase) throws XAException {
+        xaResource.commit(xid, onePhase);
+    }
+
+    public void end(Xid xid, int flags) throws XAException {
+        xaResource.end(xid, flags);
+    }
+
+    public void forget(Xid xid) throws XAException {
+        xaResource.forget(xid);
+    }
+
+    public int getTransactionTimeout() throws XAException {
+        return xaResource.getTransactionTimeout();
+    }
+
+    public boolean isSameRM(XAResource other) throws XAException {
+        if (other instanceof WrapperNamedXAResource) {
+            return xaResource.isSameRM(((WrapperNamedXAResource)other).xaResource);
+        }
+        return false;
+    }
+
+    public int prepare(Xid xid) throws XAException {
+        return xaResource.prepare(xid);
+    }
+
+    public Xid[] recover(int flag) throws XAException {
+        return xaResource.recover(flag);
+    }
+
+    public void rollback(Xid xid) throws XAException {
+        xaResource.rollback(xid);
+    }
+
+    public boolean setTransactionTimeout(int seconds) throws XAException {
+        return xaResource.setTransactionTimeout(seconds);
+    }
+
+    public void start(Xid xid, int flags) throws XAException {
+        xaResource.start(xid, flags);
+    }
+}
+
+
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XAWork.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XAWork.java
new file mode 100644
index 0000000..e0f5e21
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XAWork.java
@@ -0,0 +1,36 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import javax.transaction.xa.Xid;
+import javax.transaction.xa.XAException;
+import javax.transaction.SystemException;
+import javax.transaction.InvalidTransactionException;
+
+import org.apache.geronimo.transaction.manager.ImportedTransactionActiveException;
+
+/**
+ * primarily an interface between the WorkManager/ExecutionContext and the tm.
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public interface XAWork {
+    void begin(Xid xid, long txTimeout) throws XAException, InvalidTransactionException, SystemException, ImportedTransactionActiveException;
+    void end(Xid xid) throws XAException, SystemException;
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XidFactory.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XidFactory.java
new file mode 100644
index 0000000..ea6f51f
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XidFactory.java
@@ -0,0 +1,38 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import javax.transaction.xa.Xid;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public interface XidFactory {
+    Xid createXid();
+
+    Xid createBranch(Xid globalId, int branch);
+
+    boolean matchesGlobalId(byte[] globalTransactionId);
+
+    boolean matchesBranchId(byte[] branchQualifier);
+
+    Xid recover(int formatId, byte[] globalTransactionid, byte[] branchQualifier);
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XidFactoryImpl.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XidFactoryImpl.java
new file mode 100644
index 0000000..6412438
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XidFactoryImpl.java
@@ -0,0 +1,111 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import javax.transaction.xa.Xid;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Factory for transaction ids.
+ * The Xid is constructed of three parts:
+ * <ol><li>8 byte count (LSB first)</li>
+ * <li>4 byte system id</li>
+ * <li>4 or 16 byte IP address of host</li>
+ * <ol>
+ * @version $Rev$ $Date$
+ * todo Should have a way of setting baseId
+ */
+public class XidFactoryImpl implements XidFactory {
+    private final byte[] baseId = new byte[Xid.MAXGTRIDSIZE];
+    private long count = 1;
+
+    public XidFactoryImpl(byte[] tmId) {
+       System.arraycopy(tmId, 0, baseId, 8, tmId.length);
+    }
+
+    public XidFactoryImpl() {
+        byte[] hostid;
+        try {
+            hostid = InetAddress.getLocalHost().getAddress();
+        } catch (UnknownHostException e) {
+            hostid = new byte[]{127, 0, 0, 1};
+        }
+        int uid = System.identityHashCode(this);
+        baseId[8] = (byte) uid;
+        baseId[9] = (byte) (uid >>> 8);
+        baseId[10] = (byte) (uid >>> 16);
+        baseId[11] = (byte) (uid >>> 24);
+        System.arraycopy(hostid, 0, baseId, 12, hostid.length);
+    }
+
+    public Xid createXid() {
+        byte[] globalId = (byte[]) baseId.clone();
+        long id;
+        synchronized (this) {
+            id = count++;
+        }
+        globalId[0] = (byte) id;
+        globalId[1] = (byte) (id >>> 8);
+        globalId[2] = (byte) (id >>> 16);
+        globalId[3] = (byte) (id >>> 24);
+        globalId[4] = (byte) (id >>> 32);
+        globalId[5] = (byte) (id >>> 40);
+        globalId[6] = (byte) (id >>> 48);
+        globalId[7] = (byte) (id >>> 56);
+        return new XidImpl(globalId);
+    }
+
+    public Xid createBranch(Xid globalId, int branch) {
+        byte[] branchId = (byte[]) baseId.clone();
+        branchId[0] = (byte) branch;
+        branchId[1] = (byte) (branch >>> 8);
+        branchId[2] = (byte) (branch >>> 16);
+        branchId[3] = (byte) (branch >>> 24);
+        return new XidImpl(globalId, branchId);
+    }
+
+    public boolean matchesGlobalId(byte[] globalTransactionId) {
+        if (globalTransactionId.length != Xid.MAXGTRIDSIZE) {
+            return false;
+        }
+        for (int i = 8; i < globalTransactionId.length; i++) {
+            if (globalTransactionId[i] != baseId[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean matchesBranchId(byte[] branchQualifier) {
+        if (branchQualifier.length != Xid.MAXBQUALSIZE) {
+            return false;
+        }
+        for (int i = 8; i < branchQualifier.length; i++) {
+            if (branchQualifier[i] != baseId[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public Xid recover(int formatId, byte[] globalTransactionid, byte[] branchQualifier) {
+        return new XidImpl(formatId, globalTransactionid, branchQualifier);
+    }
+
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XidImpl.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XidImpl.java
new file mode 100644
index 0000000..b73e24e
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XidImpl.java
@@ -0,0 +1,121 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import javax.transaction.xa.Xid;
+
+/**
+ * Unique id for a transaction.
+ *
+ * @version $Rev$ $Date$
+ */
+public class XidImpl implements Xid, Serializable {
+    private static int FORMAT_ID = 0x4765526f;  // Gero
+    private final int formatId;
+    private final byte[] globalId;
+    private final byte[] branchId;
+    private int hash;   //apparently never used by our code, so don't compute it.
+
+    /**
+     * Constructor taking a global id (for the main transaction)
+     * @param globalId the global transaction id
+     */
+    public XidImpl(byte[] globalId) {
+        this.formatId = FORMAT_ID;
+        this.globalId = globalId;
+        //this.hash = hash(0, globalId);
+        branchId = new byte[Xid.MAXBQUALSIZE];
+    }
+
+    /**
+     * Constructor for a branch id
+     * @param global the xid of the global transaction this branch belongs to
+     * @param branch the branch id
+     */
+    public XidImpl(Xid global, byte[] branch) {
+        this.formatId = FORMAT_ID;
+        //int hash;
+        if (global instanceof XidImpl) {
+            globalId = ((XidImpl) global).globalId;
+            //hash = ((XidImpl) global).hash;
+        } else {
+            globalId = global.getGlobalTransactionId();
+            //hash = hash(0, globalId);
+        }
+        branchId = branch;
+        //this.hash = hash(hash, branchId);
+    }
+
+    public XidImpl(int formatId, byte[] globalId, byte[] branchId) {
+        this.formatId = formatId;
+        this.globalId = globalId;
+        this.branchId = branchId;
+    }
+
+    private int hash(int hash, byte[] id) {
+        for (int i = 0; i < id.length; i++) {
+            hash = (hash * 37) + id[i];
+        }
+        return hash;
+    }
+
+    public int getFormatId() {
+        return formatId;
+    }
+
+    public byte[] getGlobalTransactionId() {
+        return (byte[]) globalId.clone();
+    }
+
+    public byte[] getBranchQualifier() {
+        return (byte[]) branchId.clone();
+    }
+
+    public boolean equals(Object obj) {
+        if (obj instanceof XidImpl == false) {
+            return false;
+        }
+        XidImpl other = (XidImpl) obj;
+        return formatId == other.formatId
+                && Arrays.equals(globalId, other.globalId)
+                && Arrays.equals(branchId, other.branchId);
+    }
+
+    public int hashCode() {
+        if (hash == 0) {
+            hash = hash(hash(0, globalId), branchId);
+        }
+        return hash;
+    }
+
+    public String toString() {
+        StringBuffer s = new StringBuffer();
+        s.append("[globalId=");
+        for (int i = 0; i < globalId.length; i++) {
+            s.append(Integer.toHexString(globalId[i]));
+        }
+        s.append(",branchId=");
+        for (int i = 0; i < branchId.length; i++) {
+            s.append(Integer.toHexString(branchId[i]));
+        }
+        s.append("]");
+        return s.toString();
+    }
+}
diff --git a/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XidImporter.java b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XidImporter.java
new file mode 100644
index 0000000..707d9fe
--- /dev/null
+++ b/geronimo-transaction/src/main/java/org/apache/geronimo/transaction/manager/XidImporter.java
@@ -0,0 +1,43 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.util.Map;
+
+import javax.transaction.xa.Xid;
+import javax.transaction.xa.XAException;
+import javax.transaction.Transaction;
+import javax.transaction.SystemException;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public interface XidImporter {
+
+    Transaction importXid(Xid xid, long transactionTimeoutMillis) throws XAException, SystemException;
+
+    void commit(Transaction tx, boolean onePhase) throws XAException;
+    void forget(Transaction tx) throws XAException;
+    int prepare(Transaction tx) throws XAException;
+    void rollback(Transaction tx) throws XAException;
+
+    Map getExternalXids();
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/context/GeronimoTransactionManagerTest.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/context/GeronimoTransactionManagerTest.java
new file mode 100644
index 0000000..4e720d3
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/context/GeronimoTransactionManagerTest.java
@@ -0,0 +1,121 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.context;
+
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import java.util.concurrent.CountDownLatch;
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+import org.apache.geronimo.transaction.manager.ImportedTransactionActiveException;
+import org.apache.geronimo.transaction.manager.GeronimoTransactionManager;
+import org.apache.geronimo.transaction.manager.XidFactory;
+import org.apache.geronimo.transaction.manager.XidFactoryImpl;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ */
+public class GeronimoTransactionManagerTest extends TestCase {
+
+    private GeronimoTransactionManager geronimoTransactionManager;
+    private XidFactory xidFactory = new XidFactoryImpl("geronimo.test.tm".getBytes());
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        geronimoTransactionManager = new GeronimoTransactionManager();
+    }
+
+    protected void tearDown() throws Exception {
+        geronimoTransactionManager = null;
+        super.tearDown();
+    }
+
+    public void testImportedTxLifecycle() throws Exception {
+        Xid xid = xidFactory.createXid();
+        geronimoTransactionManager.begin(xid, 1000);
+        geronimoTransactionManager.end(xid);
+        geronimoTransactionManager.begin(xid, 1000);
+        geronimoTransactionManager.end(xid);
+        int readOnly = geronimoTransactionManager.prepare(xid);
+        assertEquals(XAResource.XA_RDONLY, readOnly);
+//        geronimoTransactionManager.commit(xid, false);
+    }
+
+    public void testNoConcurrentWorkSameXid() throws Exception {
+        final Xid xid = xidFactory.createXid();
+
+        final CountDownLatch startSignal = new CountDownLatch(1);
+        final CountDownLatch cleanupSignal = new CountDownLatch(1);
+        final CountDownLatch endSignal = new CountDownLatch(1);
+
+        new Thread() {
+            public void run() {
+                try {
+                    try {
+                        try {
+                            geronimoTransactionManager.begin(xid, 1000);
+                        } finally {
+                            startSignal.countDown();
+                        }
+                        cleanupSignal.await();
+                        geronimoTransactionManager.end(xid);
+                        geronimoTransactionManager.rollback(xid);
+                    } finally {
+                        endSignal.countDown();
+                    }
+                } catch (Exception e) {
+                    throw (AssertionFailedError) new AssertionFailedError().initCause(e);
+                }
+            }
+        }.start();
+
+        // wait for thread to begin the tx
+        startSignal.await();
+        try {
+            geronimoTransactionManager.begin(xid, 1000);
+            fail("should not be able begin same xid twice");
+        } catch (ImportedTransactionActiveException e) {
+            //expected
+        } finally {
+            // tell thread to start cleanup (e.g., end and rollback the tx)
+            cleanupSignal.countDown();
+
+            // wait for our thread to finish cleanup
+            endSignal.await();
+        }
+    }
+
+    public void testOnlyOneImportedTxAtATime() throws Exception {
+        Xid xid1 = xidFactory.createXid();
+        Xid xid2 = xidFactory.createXid();
+        geronimoTransactionManager.begin(xid1, 1000);
+        try {
+            geronimoTransactionManager.begin(xid2, 1000);
+            fail("should not be able to begin a 2nd tx without ending the first");
+        } catch (IllegalStateException e) {
+            //expected
+        } finally {
+            geronimoTransactionManager.end(xid1);
+            geronimoTransactionManager.rollback(xid1);
+        }
+    }
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/log/AbstractLogTest.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/log/AbstractLogTest.java
new file mode 100644
index 0000000..b302c66
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/log/AbstractLogTest.java
@@ -0,0 +1,195 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.log;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collections;
+import java.util.List;
+
+import javax.transaction.xa.Xid;
+
+import junit.framework.TestCase;
+import org.apache.geronimo.transaction.manager.TransactionLog;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public abstract class AbstractLogTest extends TestCase {
+    private Object startBarrier = new Object();
+    private Object stopBarrier = new Object();
+    private int startedThreads = 0;
+    private int stoppedThreads = 0;
+    long totalDuration = 0;
+    private Xid xid;
+    private List names;
+    final Object mutex = new Object();
+    long totalXidCount = 0;
+    private Writer resultsXML;
+    private Writer resultsCSV;
+
+    public void testDummy() throws Exception {}
+
+    public void testTransactionLog() throws Exception {
+        File resultFileXML = new File(getResultFileName() + ".xml");
+        resultsXML = new FileWriter(resultFileXML);
+        resultsXML.write("<log-test>\n");
+        File resultFileCSV = new File(getResultFileName() + ".csv");
+        resultsCSV = new FileWriter(resultFileCSV);
+        resultsCSV.write("workerCount,xidCount,TotalXids,missingXids,DurationMilliseconds,XidsPerSecond,AverageForceTime,AverageBytesPerForce,AverageLatency\n");
+        int xidCount = Integer.getInteger("xa.log.test.xid.count", 50).intValue();
+        int minWorkerCount = Integer.getInteger("xa.log.test.worker.count.min", 20).intValue();
+        int maxWorkerCount = Integer.getInteger("xa.log.test.worker.count.max", 40).intValue();
+        int workerCountStep = Integer.getInteger("xa.log.test.worker.count.step", 20).intValue();
+        int repCount = Integer.getInteger("xa.log.test.repetition.count", 1).intValue();
+        long maxTime = Long.getLong("xa.log.test.max.time.seconds", 30).longValue() * 1000;
+        int overtime = 0;
+        try {
+            for (int workers = minWorkerCount; workers <= maxWorkerCount; workers += workerCountStep) {
+                for (int reps = 0; reps < repCount; reps++) {
+                    if (testTransactionLog(workers, xidCount) > maxTime) {
+                        overtime++;
+                        if (overtime > 1) {
+                            return;
+                        }
+                    }
+                    resultsCSV.flush();
+                    resultsXML.flush();
+                }
+            }
+        } finally {
+            resultsXML.write("</log-test>\n");
+            resultsXML.flush();
+            resultsXML.close();
+            resultsCSV.flush();
+            resultsCSV.close();
+        }
+    }
+
+    protected abstract String getResultFileName();
+
+    public long testTransactionLog(int workers, int xidCount) throws Exception {
+        TransactionLog transactionLog = createTransactionLog();
+
+        xid = new XidImpl2(new byte[Xid.MAXGTRIDSIZE]);
+        //TODO Supply an actual list
+        names = Collections.EMPTY_LIST;
+
+        long startTime = journalTest(transactionLog, workers, xidCount);
+
+        long stopTime = System.currentTimeMillis();
+
+        printSpeedReport(transactionLog, startTime, stopTime, workers, xidCount);
+        closeTransactionLog(transactionLog);
+        return stopTime - startTime;
+    }
+
+    protected abstract void closeTransactionLog(TransactionLog transactionLog) throws Exception ;
+
+    protected abstract TransactionLog createTransactionLog() throws Exception;
+
+    private long journalTest(final TransactionLog logger, final int workers, final int xidCount)
+            throws Exception {
+        totalXidCount = 0;
+        startedThreads = 0;
+        stoppedThreads = 0;
+        totalDuration = 0;
+        for (int i = 0; i < workers; i++) {
+            new Thread() {
+                public void run() {
+                    long localXidCount = 0;
+                    boolean exception = false;
+                    long localDuration = 0;
+                    try {
+                        synchronized (startBarrier) {
+                            ++startedThreads;
+                            startBarrier.notifyAll();
+                            while (startedThreads < (workers + 1)) startBarrier.wait();
+                        }
+                        long localStartTime = System.currentTimeMillis();
+
+                        for (int i = 0; i < xidCount; i++) {
+                            // journalize COMMITTING record
+                            Object logMark = logger.prepare(xid, names);
+                            //localXidCount++;
+
+                            // journalize FORGET record
+                            logger.commit(xid, logMark);
+                            localXidCount++;
+                        }
+                        localDuration = System.currentTimeMillis() - localStartTime;
+                    } catch (Exception e) {
+                        //
+                        // FIXME: Remove System.err usage
+                        //
+                        
+                        System.err.println(Thread.currentThread().getName());
+                        e.printStackTrace(System.err);
+                        exception = true;
+                    } finally {
+                        synchronized (mutex) {
+                            totalXidCount += localXidCount;
+                            totalDuration += localDuration;
+                        }
+                        synchronized (stopBarrier) {
+                            ++stoppedThreads;
+                            stopBarrier.notifyAll();
+                        }
+                    }
+
+                }
+            }
+                    .start();
+        }
+
+        // Wait for all the workers to be ready..
+        long startTime = 0;
+        synchronized (startBarrier) {
+            while (startedThreads < workers) startBarrier.wait();
+            ++startedThreads;
+            startBarrier.notifyAll();
+            startTime = System.currentTimeMillis();
+        }
+
+        // Wait for all the workers to finish.
+        synchronized (stopBarrier) {
+            while (stoppedThreads < workers) stopBarrier.wait();
+        }
+
+        return startTime;
+
+    }
+
+    void printSpeedReport(TransactionLog logger, long startTime, long stopTime, int workers, int xidCount) throws IOException {
+        long mc = ((long) xidCount) * workers;
+        long duration = (stopTime - startTime);
+        long xidsPerSecond = (totalXidCount * 1000 / (duration));
+        int averageForceTime = logger.getAverageForceTime();
+        int averageBytesPerForce = logger.getAverageBytesPerForce();
+        long averageLatency = totalDuration/totalXidCount;
+        resultsXML.write("<run><workers>" + workers + "</workers><xids-per-thread>" + xidCount + "</xids-per-thread><expected-total-xids>" + mc + "</expected-total-xids><missing-xids>" + (mc - totalXidCount) + "</missing-xids><totalDuration-milliseconds>" + duration + "</totalDuration-milliseconds><xids-per-second>" + xidsPerSecond + "</xids-per-second></run>\n");
+        resultsXML.write(logger.getXMLStats() + "\n");
+        resultsCSV.write("" + workers + "," + xidCount + "," + mc + "," + (mc - totalXidCount) + "," + duration + "," + xidsPerSecond + "," + averageForceTime + "," + averageBytesPerForce  + "," + averageLatency + "\n");
+
+    }
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/log/HOWLLogTest.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/log/HOWLLogTest.java
new file mode 100644
index 0000000..93da6ec
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/log/HOWLLogTest.java
@@ -0,0 +1,86 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.log;
+
+import java.io.File;
+
+import junit.extensions.TestSetup;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.geronimo.transaction.manager.TransactionLog;
+import org.apache.geronimo.transaction.manager.XidFactory;
+import org.apache.geronimo.transaction.manager.XidFactoryImpl;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class HOWLLogTest extends AbstractLogTest {
+    private static final File basedir = new File(System.getProperty("basedir", System.getProperty("user.dir")));
+    private static final String LOG_FILE_NAME = "howl_test";
+
+
+    protected String getResultFileName() {
+        return "howllog";
+    }
+
+    protected void closeTransactionLog(TransactionLog transactionLog) throws Exception {
+        ((HOWLLog) transactionLog).doStop();
+    }
+
+
+    protected TransactionLog createTransactionLog() throws Exception {
+        XidFactory xidFactory = new XidFactoryImpl();
+        HOWLLog howlLog = new HOWLLog(
+                "org.objectweb.howl.log.BlockLogBuffer", //                "bufferClassName",
+                4, //                "bufferSizeKBytes",
+                true, //                "checksumEnabled",
+                true, //                "adler32Checksum",
+                20, //                "flushSleepTime",
+                "txlog", //                "logFileDir",
+                "log", //                "logFileExt",
+                LOG_FILE_NAME, //                "logFileName",
+                200, //                "maxBlocksPerFile",
+                10, //                "maxBuffers",
+                2, //                "maxLogFiles",
+                2, //                "minBuffers",
+                10,//                "threadsWaitingForceThreshold"});
+                xidFactory,
+                new File(basedir, "target")
+        );
+        howlLog.doStart();
+        return howlLog;
+    }
+    public static Test suite() {
+        return new TestSetup(new TestSuite(HOWLLogTest.class)) {
+            protected void setUp() throws Exception {
+                File logFile = new File(basedir, "target/" + LOG_FILE_NAME + "_1.log");
+                if (logFile.exists()) {
+                    logFile.delete();
+                }
+                logFile = new File(basedir, "target/" + LOG_FILE_NAME + "_2.log");
+                if (logFile.exists()) {
+                    logFile.delete();
+                }
+            }
+        };
+    }
+
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/AbstractRecoveryTest.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/AbstractRecoveryTest.java
new file mode 100644
index 0000000..a6bc608
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/AbstractRecoveryTest.java
@@ -0,0 +1,251 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import junit.framework.TestCase;
+
+/**
+ * This is just a unit test for recovery, depending on proper behavior of the log(s) it uses.
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public abstract class AbstractRecoveryTest extends TestCase {
+
+    protected TransactionLog txLog;
+
+    protected final XidFactory xidFactory = new XidFactoryImpl();
+    private static final String RM1 = "rm1";
+    private static final String RM2 = "rm2";
+    private static final String RM3 = "rm3";
+    private static final int XID_COUNT = 3;
+    private int branchCounter;
+
+    public void testDummy() throws Exception {}
+
+    public void test2ResOnlineAfterRecoveryStart() throws Exception {
+        Xid[] xids = getXidArray(XID_COUNT);
+        MockXAResource xares1 = new MockXAResource(RM1, xids);
+        MockXAResource xares2 = new MockXAResource(RM2, xids);
+        MockTransactionInfo[] txInfos = makeTxInfos(xids);
+        addBranch(txInfos, xares1);
+        addBranch(txInfos, xares2);
+        prepareLog(txLog, txInfos);
+        prepareForReplay();
+        Recovery recovery = new RecoveryImpl(txLog, xidFactory);
+        recovery.recoverLog();
+        assertTrue(!recovery.hasRecoveryErrors());
+        assertTrue(recovery.getExternalXids().isEmpty());
+        assertTrue(!recovery.localRecoveryComplete());
+        recovery.recoverResourceManager(xares1);
+        assertTrue(!recovery.localRecoveryComplete());
+        assertEquals(XID_COUNT, xares1.committed.size());
+        recovery.recoverResourceManager(xares2);
+        assertEquals(XID_COUNT, xares2.committed.size());
+        assertTrue(recovery.localRecoveryComplete());
+    }
+
+    public void test3ResOnlineAfterRecoveryStart() throws Exception {
+        Xid[] xids12 = getXidArray(XID_COUNT);
+        List xids12List = Arrays.asList(xids12);
+        Xid[] xids13 = getXidArray(XID_COUNT);
+        List xids13List = Arrays.asList(xids13);
+        Xid[] xids23 = getXidArray(XID_COUNT);
+        List xids23List = Arrays.asList(xids23);
+        ArrayList tmp = new ArrayList();
+        tmp.addAll(xids12List);
+        tmp.addAll(xids13List);
+        Xid[] xids1 = (Xid[]) tmp.toArray(new Xid[6]);
+        tmp.clear();
+        tmp.addAll(xids12List);
+        tmp.addAll(xids23List);
+        Xid[] xids2 = (Xid[]) tmp.toArray(new Xid[6]);
+        tmp.clear();
+        tmp.addAll(xids13List);
+        tmp.addAll(xids23List);
+        Xid[] xids3 = (Xid[]) tmp.toArray(new Xid[6]);
+
+        MockXAResource xares1 = new MockXAResource(RM1, xids1);
+        MockXAResource xares2 = new MockXAResource(RM2, xids2);
+        MockXAResource xares3 = new MockXAResource(RM3, xids3);
+        MockTransactionInfo[] txInfos12 = makeTxInfos(xids12);
+        addBranch(txInfos12, xares1);
+        addBranch(txInfos12, xares2);
+        prepareLog(txLog, txInfos12);
+        MockTransactionInfo[] txInfos13 = makeTxInfos(xids13);
+        addBranch(txInfos13, xares1);
+        addBranch(txInfos13, xares3);
+        prepareLog(txLog, txInfos13);
+        MockTransactionInfo[] txInfos23 = makeTxInfos(xids23);
+        addBranch(txInfos23, xares2);
+        addBranch(txInfos23, xares3);
+        prepareLog(txLog, txInfos23);
+        prepareForReplay();
+        Recovery recovery = new RecoveryImpl(txLog, xidFactory);
+        recovery.recoverLog();
+        assertTrue(!recovery.hasRecoveryErrors());
+        assertTrue(recovery.getExternalXids().isEmpty());
+        assertEquals(XID_COUNT * 3, recovery.localUnrecoveredCount());
+        recovery.recoverResourceManager(xares1);
+        assertEquals(XID_COUNT * 3, recovery.localUnrecoveredCount());
+        assertEquals(XID_COUNT * 2, xares1.committed.size());
+        recovery.recoverResourceManager(xares2);
+        assertEquals(XID_COUNT * 2, recovery.localUnrecoveredCount());
+        assertEquals(XID_COUNT * 2, xares2.committed.size());
+        recovery.recoverResourceManager(xares3);
+        assertEquals(0, recovery.localUnrecoveredCount());
+        assertEquals(XID_COUNT * 2, xares3.committed.size());
+
+    }
+
+    protected abstract void prepareForReplay() throws Exception;
+
+    private void prepareLog(TransactionLog txLog, MockTransactionInfo[] txInfos) throws LogException {
+        for (int i = 0; i < txInfos.length; i++) {
+            MockTransactionInfo txInfo = txInfos[i];
+            txLog.prepare(txInfo.globalXid, txInfo.branches);
+        }
+    }
+
+
+    private Xid[] getXidArray(int i) {
+        Xid[] xids = new Xid[i];
+        for (int j = 0; j < xids.length; j++) {
+            xids[j] = xidFactory.createXid();
+        }
+        return xids;
+    }
+
+    private void addBranch(MockTransactionInfo[] txInfos, MockXAResource xaRes) {
+        for (int i = 0; i < txInfos.length; i++) {
+            MockTransactionInfo txInfo = txInfos[i];
+            Xid globalXid = txInfo.globalXid;
+            Xid branchXid = xidFactory.createBranch(globalXid, branchCounter++);
+            txInfo.branches.add(new MockTransactionBranchInfo(xaRes.getName(), branchXid));
+        }
+    }
+
+    private MockTransactionInfo[] makeTxInfos(Xid[] xids) {
+        MockTransactionInfo[] txInfos = new MockTransactionInfo[xids.length];
+        for (int i = 0; i < xids.length; i++) {
+            Xid xid = xids[i];
+            txInfos[i] = new MockTransactionInfo(xid, new ArrayList());
+        }
+        return txInfos;
+    }
+
+    private static class MockXAResource implements NamedXAResource {
+
+        private final String name;
+        private final Xid[] xids;
+        private final List committed = new ArrayList();
+        private final List rolledBack = new ArrayList();
+
+        public MockXAResource(String name, Xid[] xids) {
+            this.name = name;
+            this.xids = xids;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void commit(Xid xid, boolean onePhase) throws XAException {
+            committed.add(xid);
+        }
+
+        public void end(Xid xid, int flags) throws XAException {
+        }
+
+        public void forget(Xid xid) throws XAException {
+        }
+
+        public int getTransactionTimeout() throws XAException {
+            return 0;
+        }
+
+        public boolean isSameRM(XAResource xaResource) throws XAException {
+            return false;
+        }
+
+        public int prepare(Xid xid) throws XAException {
+            return 0;
+        }
+
+        public Xid[] recover(int flag) throws XAException {
+            return xids;
+        }
+
+        public void rollback(Xid xid) throws XAException {
+            rolledBack.add(xid);
+        }
+
+        public boolean setTransactionTimeout(int seconds) throws XAException {
+            return false;
+        }
+
+        public void start(Xid xid, int flags) throws XAException {
+        }
+
+        public List getCommitted() {
+            return committed;
+        }
+
+        public List getRolledBack() {
+            return rolledBack;
+        }
+
+
+    }
+
+    private static class MockTransactionInfo {
+        private Xid globalXid;
+        private List branches;
+
+        public MockTransactionInfo(Xid globalXid, List branches) {
+            this.globalXid = globalXid;
+            this.branches = branches;
+        }
+    }
+
+    private static class MockTransactionBranchInfo implements TransactionBranchInfo {
+        private final String name;
+        private final Xid branchXid;
+
+        public MockTransactionBranchInfo(String name, Xid branchXid) {
+            this.name = name;
+            this.branchXid = branchXid;
+        }
+
+        public String getResourceName() {
+            return name;
+        }
+
+        public Xid getBranchXid() {
+            return branchXid;
+        }
+    }
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/HOWLLogRecoveryTest.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/HOWLLogRecoveryTest.java
new file mode 100644
index 0000000..af9e764
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/HOWLLogRecoveryTest.java
@@ -0,0 +1,105 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.io.File;
+
+import org.apache.geronimo.transaction.log.HOWLLog;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import junit.extensions.TestSetup;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class HOWLLogRecoveryTest extends AbstractRecoveryTest {
+    private static final File basedir = new File(System.getProperty("basedir", System.getProperty("user.dir")));
+    private static final String LOG_FILE_NAME = "howl_test_";
+    private static final String logFileDir = "txlog";
+    private static final String targetDir = new File(basedir, "target").getAbsolutePath();
+    private static final File txlogDir = new File(basedir, "target/" + logFileDir);
+
+    public void test2Again() throws Exception {
+        test2ResOnlineAfterRecoveryStart();
+    }
+
+    public void test3Again() throws Exception {
+        test3ResOnlineAfterRecoveryStart();
+    }
+
+    protected void setUp() throws Exception {
+        // Deletes the previous transaction log files.
+        File[] files = txlogDir.listFiles();
+        if ( null != files ) {
+            for (int i = 0; i < files.length; i++) {
+                files[i].delete();
+            }
+        }
+        setUpHowlLog();
+    }
+
+    private void setUpHowlLog() throws Exception {
+        HOWLLog howlLog = new HOWLLog(
+                "org.objectweb.howl.log.BlockLogBuffer", //                "bufferClassName",
+                4, //                "bufferSizeKBytes",
+                true, //                "checksumEnabled",
+                true, //                "adler32Checksum",
+                20, //                "flushSleepTime",
+                logFileDir, //                "logFileDir",
+                "log", //                "logFileExt",
+                LOG_FILE_NAME, //                "logFileName",
+                200, //                "maxBlocksPerFile",
+                10, //                "maxBuffers",                       log
+                2, //                "maxLogFiles",
+                2, //                "minBuffers",
+                10,//                "threadsWaitingForceThreshold"});
+                xidFactory,
+                new File(targetDir)
+        );
+        howlLog.doStart();
+        txLog = howlLog;
+    }
+
+    protected void tearDown() throws Exception {
+        ((HOWLLog)txLog).doStop();
+        txLog = null;
+    }
+
+    protected void prepareForReplay() throws Exception {
+        tearDown();
+        setUpHowlLog();
+    }
+
+    public static Test suite() {
+        return new TestSetup(new TestSuite(HOWLLogRecoveryTest.class)) {
+            protected void setUp() throws Exception {
+                File logFile = new File(txlogDir, LOG_FILE_NAME + "_1.log");
+                if (logFile.exists()) {
+                    logFile.delete();
+                }
+                logFile = new File(txlogDir, LOG_FILE_NAME + "_2.log");
+                if (logFile.exists()) {
+                    logFile.delete();
+                }
+            }
+        };
+    }
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/MockLog.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/MockLog.java
new file mode 100644
index 0000000..08c05d4
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/MockLog.java
@@ -0,0 +1,77 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.transaction.xa.Xid;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class MockLog implements TransactionLog {
+
+    final Map prepared = new HashMap();
+    final List committed = new ArrayList();
+    final List rolledBack = new ArrayList();
+
+    public void begin(Xid xid) throws LogException {
+    }
+
+    public Object prepare(Xid xid, List branches) throws LogException {
+        Object mark = new Object();
+        Recovery.XidBranchesPair xidBranchesPair = new Recovery.XidBranchesPair(xid, mark);
+        xidBranchesPair.getBranches().addAll(branches);
+        prepared.put(xid, xidBranchesPair);
+        return mark;
+    }
+
+    public void commit(Xid xid, Object logMark) throws LogException {
+        committed.add(xid);
+    }
+
+    public void rollback(Xid xid, Object logMark) throws LogException {
+        rolledBack.add(xid);
+    }
+
+    public Collection recover(XidFactory xidFactory) throws LogException {
+        Map copy = new HashMap(prepared);
+        copy.keySet().removeAll(committed);
+        copy.keySet().removeAll(rolledBack);
+        return copy.values();
+    }
+
+    public String getXMLStats() {
+        return null;
+    }
+
+    public int getAverageForceTime() {
+        return 0;
+    }
+
+    public int getAverageBytesPerForce() {
+        return 0;
+    }
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/MockLogRecoveryTest.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/MockLogRecoveryTest.java
new file mode 100644
index 0000000..9715107
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/MockLogRecoveryTest.java
@@ -0,0 +1,38 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class MockLogRecoveryTest extends AbstractRecoveryTest {
+
+    protected void setUp() throws Exception {
+        txLog = new MockLog();
+    }
+
+    protected void tearDown() throws Exception {
+        txLog = null;
+    }
+
+    protected void prepareForReplay() throws Exception {
+    }
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/MockResource.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/MockResource.java
new file mode 100644
index 0000000..2148978
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/MockResource.java
@@ -0,0 +1,156 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.util.Set;
+import java.util.HashSet;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class MockResource implements NamedXAResource {
+    private String xaResourceName = "mockResource";
+    private Xid currentXid;
+    private MockResourceManager manager;
+    private int timeout = 0;
+    private boolean prepared;
+    private boolean committed;
+    private boolean rolledback;
+    private Set preparedXids = new HashSet();
+    private Set knownXids = new HashSet();
+    private Set finishedXids = new HashSet();//end was called with TMSUCCESS or TMFAIL
+
+    public MockResource(MockResourceManager manager, String xaResourceName) {
+        this.manager = manager;
+        this.xaResourceName = xaResourceName;
+    }
+
+    public int getTransactionTimeout() throws XAException {
+        return timeout;
+    }
+
+    public boolean setTransactionTimeout(int seconds) throws XAException {
+        return false;
+    }
+
+    public Xid getCurrentXid() {
+        return currentXid;
+    }
+
+    public void start(Xid xid, int flags) throws XAException {
+        if (this.currentXid != null) {
+            throw new XAException(XAException.XAER_PROTO);
+        }
+        if (flags == XAResource.TMRESUME && !knownXids.contains(xid)) {
+            throw new XAException(XAException.XAER_PROTO);
+        }
+        if (finishedXids.contains(xid)) {
+            throw new XAException(XAException.XAER_PROTO);
+        }
+        if ((flags & XAResource.TMJOIN) != 0) {
+            manager.join(xid, this);
+        } else {
+            manager.newTx(xid, this);
+        }
+        this.currentXid = xid;
+        if (!knownXids.contains(xid)) {
+            knownXids.add(xid);
+        }
+    }
+
+    public void end(Xid xid, int flags) throws XAException {
+        if (!knownXids.contains(xid)) {
+            throw new XAException(XAException.XAER_PROTO);
+        }
+        if (flags == XAResource.TMSUSPEND) {
+            if (currentXid == null) {
+                throw new XAException(XAException.XAER_PROTO);
+            } else if (this.currentXid != xid) {
+                throw new XAException(XAException.XAER_PROTO);
+            }
+        } else if (flags == XAResource.TMFAIL || flags == XAResource.TMSUCCESS) {
+            if (finishedXids.contains(xid)) {
+                throw new XAException(XAException.XAER_PROTO);
+            }
+            finishedXids.add(xid);
+        }
+        this.currentXid = null;
+    }
+
+    public int prepare(Xid xid) throws XAException {
+        if (!finishedXids.contains(xid)) {
+            throw new XAException(XAException.XAER_PROTO);
+        }
+        prepared = true;
+        preparedXids.add(xid);
+        return XAResource.XA_OK;
+    }
+
+    public void commit(Xid xid, boolean onePhase) throws XAException {
+        if (!finishedXids.contains(xid)) {
+            throw new XAException(XAException.XAER_PROTO);
+        }
+        preparedXids.remove(xid);
+        committed = true;
+    }
+
+    public void rollback(Xid xid) throws XAException {
+        if (!finishedXids.contains(xid)) {
+            throw new XAException(XAException.XAER_PROTO);
+        }
+        rolledback = true;
+        preparedXids.remove(xid);
+        manager.forget(xid, this);
+    }
+
+    public boolean isSameRM(XAResource xaResource) throws XAException {
+        if (xaResource instanceof MockResource) {
+            return manager == ((MockResource) xaResource).manager;
+        }
+        return false;
+    }
+
+    public void forget(Xid xid) throws XAException {
+        throw new UnsupportedOperationException();
+    }
+
+    public Xid[] recover(int flag) throws XAException {
+        return (Xid[]) preparedXids.toArray(new Xid[preparedXids.size()]);
+    }
+
+    public boolean isPrepared() {
+        return prepared;
+    }
+
+    public boolean isCommitted() {
+        return committed;
+    }
+
+    public boolean isRolledback() {
+        return rolledback;
+    }
+
+    public String getName() {
+        return xaResourceName;
+    }
+
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/MockResourceManager.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/MockResourceManager.java
new file mode 100644
index 0000000..15ebcc7
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/MockResourceManager.java
@@ -0,0 +1,78 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.transaction.SystemException;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ */
+public class MockResourceManager {
+    private boolean willCommit;
+    private Map xids = new HashMap();
+
+    private NamedXAResource resources;
+
+    public MockResourceManager(boolean willCommit) {
+        this.willCommit = willCommit;
+    }
+
+    public MockResource getResource(String xaResourceName) {
+        MockResource mockResource =  new MockResource(this, xaResourceName);
+        resources = mockResource;
+        return mockResource;
+    }
+
+    public void join(Xid xid, XAResource xaRes) throws XAException {
+        Set resSet = (Set) xids.get(xid);
+        if (resSet == null) {
+            throw new XAException(XAException.XAER_NOTA);
+        }
+        resSet.add(xaRes);
+    }
+
+    public void newTx(Xid xid, XAResource xaRes) throws XAException {
+        if (xids.containsKey(xid)) {
+            throw new XAException(XAException.XAER_DUPID);
+        }
+        Set resSet = new HashSet();
+        resSet.add(xaRes);
+        xids.put(xid, resSet);
+    }
+
+    public void forget(Xid xid, XAResource xaRes) throws XAException {
+        if (xids.remove(xid) == null) {
+            throw new XAException(XAException.XAER_NOTA);
+        }
+    }
+
+    public void doRecovery(RecoverableTransactionManager transactionManager) throws SystemException {
+        transactionManager.recoverResourceManager(resources);
+    }
+
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/ProtocolTest.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/ProtocolTest.java
new file mode 100644
index 0000000..122b4a3
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/ProtocolTest.java
@@ -0,0 +1,83 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import javax.transaction.Transaction;
+import javax.transaction.xa.XAResource;
+
+import junit.framework.TestCase;
+
+/**
+ */
+public class ProtocolTest extends TestCase {
+
+    private TransactionManagerImpl tm;
+    private MockResourceManager mrm1, mrm2;
+    private MockResource mr11, mr12, mr21, mr22;
+
+    protected void setUp() throws Exception {
+        tm = new TransactionManagerImpl();
+        mrm1 = new MockResourceManager(true);
+        mrm2 = new MockResourceManager(true);
+        mr11 = new MockResource(mrm1, "mr11");
+        mr12 = new MockResource(mrm1, "mr12");
+        mr21 = new MockResource(mrm2, "mr21");
+        mr22 = new MockResource(mrm2, "mr22");
+    }
+
+    public void testOnePhaseCommit() throws Exception {
+        tm.begin();
+        Transaction tx = tm.getTransaction();
+        tx.enlistResource(mr11);
+        tx.delistResource(mr11, XAResource.TMSUSPEND);
+        tm.commit();
+    }
+
+    public void testOnePhaseCommiTwoResources() throws Exception {
+        tm.begin();
+        Transaction tx = tm.getTransaction();
+        tx.enlistResource(mr11);
+        tx.delistResource(mr11, XAResource.TMSUSPEND);
+        tx.enlistResource(mr12);
+        tx.delistResource(mr12, XAResource.TMSUSPEND);
+        tm.commit();
+    }
+    public void testTwoPhaseCommit() throws Exception {
+        tm.begin();
+        Transaction tx = tm.getTransaction();
+        tx.enlistResource(mr11);
+        tx.delistResource(mr11, XAResource.TMSUSPEND);
+        tx.enlistResource(mr21);
+        tx.delistResource(mr21, XAResource.TMSUSPEND);
+        tm.commit();
+    }
+    public void testTwoPhaseCommit4Resources() throws Exception {
+        tm.begin();
+        Transaction tx = tm.getTransaction();
+        tx.enlistResource(mr11);
+        tx.delistResource(mr11, XAResource.TMSUSPEND);
+        tx.enlistResource(mr12);
+        tx.delistResource(mr12, XAResource.TMSUSPEND);
+        tx.enlistResource(mr21);
+        tx.delistResource(mr21, XAResource.TMSUSPEND);
+        tx.enlistResource(mr22);
+        tx.delistResource(mr22, XAResource.TMSUSPEND);
+        tm.commit();
+    }
+
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/RecoveryTest.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/RecoveryTest.java
new file mode 100644
index 0000000..441c0e1
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/RecoveryTest.java
@@ -0,0 +1,262 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import junit.framework.TestCase;
+
+/**
+ * This is just a unit test for recovery, depending on proper behavior of the log(s) it uses.
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class RecoveryTest extends TestCase {
+
+    XidFactory xidFactory = new XidFactoryImpl();
+    private final String RM1 = "rm1";
+    private final String RM2 = "rm2";
+    private final String RM3 = "rm3";
+
+    public void testCommittedRMToBeRecovered() throws Exception {
+        MockLog mockLog = new MockLog();
+        Xid[] xids = getXidArray(1);
+        // specifies an empty Xid array such that this XAResource has nothing
+        // to recover. This means that the presumed abort protocol has failed
+        // right after the commit of the RM and before the force-write of the
+        // committed log record.
+        MockXAResource xares1 = new MockXAResource(RM1, new Xid[0]);
+        MockTransactionInfo[] txInfos = makeTxInfos(xids);
+        addBranch(txInfos, xares1);
+        prepareLog(mockLog, txInfos);
+        Recovery recovery = new RecoveryImpl(mockLog, xidFactory);
+        recovery.recoverLog();
+        assertTrue(!recovery.hasRecoveryErrors());
+        assertTrue(recovery.getExternalXids().isEmpty());
+        assertTrue(!recovery.localRecoveryComplete());
+        recovery.recoverResourceManager(xares1);
+        assertEquals(0, xares1.committed.size());
+        assertTrue(recovery.localRecoveryComplete());
+        
+    }
+    
+    public void test2ResOnlineAfterRecoveryStart() throws Exception {
+        MockLog mockLog = new MockLog();
+        Xid[] xids = getXidArray(3);
+        MockXAResource xares1 = new MockXAResource(RM1, xids);
+        MockXAResource xares2 = new MockXAResource(RM2, xids);
+        MockTransactionInfo[] txInfos = makeTxInfos(xids);
+        addBranch(txInfos, xares1);
+        addBranch(txInfos, xares2);
+        prepareLog(mockLog, txInfos);
+        Recovery recovery = new RecoveryImpl(mockLog, xidFactory);
+        recovery.recoverLog();
+        assertTrue(!recovery.hasRecoveryErrors());
+        assertTrue(recovery.getExternalXids().isEmpty());
+        assertTrue(!recovery.localRecoveryComplete());
+        recovery.recoverResourceManager(xares1);
+        assertTrue(!recovery.localRecoveryComplete());
+        assertEquals(3, xares1.committed.size());
+        recovery.recoverResourceManager(xares2);
+        assertEquals(3, xares2.committed.size());
+        assertTrue(recovery.localRecoveryComplete());
+
+    }
+
+    private void addBranch(MockTransactionInfo[] txInfos, MockXAResource xaRes) {
+        for (int i = 0; i < txInfos.length; i++) {
+            MockTransactionInfo txInfo = txInfos[i];
+            txInfo.branches.add(new MockTransactionBranchInfo(xaRes.getName()));
+        }
+    }
+
+    private MockTransactionInfo[] makeTxInfos(Xid[] xids) {
+        MockTransactionInfo[] txInfos = new MockTransactionInfo[xids.length];
+        for (int i = 0; i < xids.length; i++) {
+            Xid xid = xids[i];
+            txInfos[i] = new MockTransactionInfo(xid, new ArrayList());
+        }
+        return txInfos;
+    }
+
+    public void test3ResOnlineAfterRecoveryStart() throws Exception {
+        MockLog mockLog = new MockLog();
+        Xid[] xids12 = getXidArray(3);
+        List xids12List = Arrays.asList(xids12);
+        Xid[] xids13 = getXidArray(3);
+        List xids13List = Arrays.asList(xids13);
+        Xid[] xids23 = getXidArray(3);
+        List xids23List = Arrays.asList(xids23);
+        ArrayList tmp = new ArrayList();
+        tmp.addAll(xids12List);
+        tmp.addAll(xids13List);
+        Xid[] xids1 = (Xid[]) tmp.toArray(new Xid[6]);
+        tmp.clear();
+        tmp.addAll(xids12List);
+        tmp.addAll(xids23List);
+        Xid[] xids2 = (Xid[]) tmp.toArray(new Xid[6]);
+        tmp.clear();
+        tmp.addAll(xids13List);
+        tmp.addAll(xids23List);
+        Xid[] xids3 = (Xid[]) tmp.toArray(new Xid[6]);
+
+        MockXAResource xares1 = new MockXAResource(RM1, xids1);
+        MockXAResource xares2 = new MockXAResource(RM2, xids2);
+        MockXAResource xares3 = new MockXAResource(RM3, xids3);
+        MockTransactionInfo[] txInfos12 = makeTxInfos(xids12);
+        addBranch(txInfos12, xares1);
+        addBranch(txInfos12, xares2);
+        prepareLog(mockLog, txInfos12);
+        MockTransactionInfo[] txInfos13 = makeTxInfos(xids13);
+        addBranch(txInfos13, xares1);
+        addBranch(txInfos13, xares3);
+        prepareLog(mockLog, txInfos13);
+        MockTransactionInfo[] txInfos23 = makeTxInfos(xids23);
+        addBranch(txInfos23, xares2);
+        addBranch(txInfos23, xares3);
+        prepareLog(mockLog, txInfos23);
+        Recovery recovery = new RecoveryImpl(mockLog, xidFactory);
+        recovery.recoverLog();
+        assertTrue(!recovery.hasRecoveryErrors());
+        assertTrue(recovery.getExternalXids().isEmpty());
+        assertEquals(9, recovery.localUnrecoveredCount());
+        recovery.recoverResourceManager(xares1);
+        assertEquals(9, recovery.localUnrecoveredCount());
+        assertEquals(6, xares1.committed.size());
+        recovery.recoverResourceManager(xares2);
+        assertEquals(6, recovery.localUnrecoveredCount());
+        assertEquals(6, xares2.committed.size());
+        recovery.recoverResourceManager(xares3);
+        assertEquals(0, recovery.localUnrecoveredCount());
+        assertEquals(6, xares3.committed.size());
+
+    }
+
+    private void prepareLog(TransactionLog txLog, MockTransactionInfo[] txInfos) throws LogException {
+        for (int i = 0; i < txInfos.length; i++) {
+            MockTransactionInfo txInfo = txInfos[i];
+            txLog.prepare(txInfo.globalXid, txInfo.branches);
+        }
+    }
+
+
+    private Xid[] getXidArray(int i) {
+        Xid[] xids = new Xid[i];
+        for (int j = 0; j < xids.length; j++) {
+            xids[j] = xidFactory.createXid();
+        }
+        return xids;
+    }
+
+    private static class MockXAResource implements NamedXAResource {
+
+        private final String name;
+        private final Xid[] xids;
+        private final List committed = new ArrayList();
+        private final List rolledBack = new ArrayList();
+
+        public MockXAResource(String name, Xid[] xids) {
+            this.name = name;
+            this.xids = xids;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void commit(Xid xid, boolean onePhase) throws XAException {
+            committed.add(xid);
+        }
+
+        public void end(Xid xid, int flags) throws XAException {
+        }
+
+        public void forget(Xid xid) throws XAException {
+        }
+
+        public int getTransactionTimeout() throws XAException {
+            return 0;
+        }
+
+        public boolean isSameRM(XAResource xaResource) throws XAException {
+            return false;
+        }
+
+        public int prepare(Xid xid) throws XAException {
+            return 0;
+        }
+
+        public Xid[] recover(int flag) throws XAException {
+            return xids;
+        }
+
+        public void rollback(Xid xid) throws XAException {
+            rolledBack.add(xid);
+        }
+
+        public boolean setTransactionTimeout(int seconds) throws XAException {
+            return false;
+        }
+
+        public void start(Xid xid, int flags) throws XAException {
+        }
+
+        public List getCommitted() {
+            return committed;
+        }
+
+        public List getRolledBack() {
+            return rolledBack;
+        }
+
+
+    }
+
+    private static class MockTransactionInfo {
+        private Xid globalXid;
+        private List branches;
+
+        public MockTransactionInfo(Xid globalXid, List branches) {
+            this.globalXid = globalXid;
+            this.branches = branches;
+        }
+    }
+
+    private static class MockTransactionBranchInfo implements TransactionBranchInfo {
+        private String name;
+
+        public MockTransactionBranchInfo(String name) {
+            this.name = name;
+        }
+
+        public String getResourceName() {
+            return name;
+        }
+
+        public Xid getBranchXid() {
+            return null;
+        }
+    }
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/TestTransactionManager.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/TestTransactionManager.java
new file mode 100644
index 0000000..558b183
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/TestTransactionManager.java
@@ -0,0 +1,120 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import javax.transaction.Status;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+
+import junit.framework.TestCase;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ */
+public class TestTransactionManager extends TestCase {
+    TransactionManager tm;
+    MockResourceManager rm1, rm2, rm3;
+
+    public void testNoTransaction() throws Exception {
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        assertNull(tm.getTransaction());
+    }
+
+    public void testNoResource() throws Exception {
+        Transaction tx;
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
+        tx = tm.getTransaction();
+        assertNotNull(tx);
+        assertEquals(Status.STATUS_ACTIVE, tx.getStatus());
+        tm.rollback();
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        assertNull(tm.getTransaction());
+        assertEquals(Status.STATUS_NO_TRANSACTION, tx.getStatus());
+
+        tm.begin();
+        assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
+        tm.rollback();
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+    }
+
+    public void testTxOp() throws Exception {
+        Transaction tx;
+        tm.begin();
+        tx = tm.getTransaction();
+        tx.rollback();
+        assertEquals(Status.STATUS_NO_TRANSACTION, tx.getStatus());
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+
+        tm.begin();
+        assertFalse(tx.equals(tm.getTransaction()));
+        tm.rollback();
+    }
+
+    public void testSuspend() throws Exception {
+        Transaction tx;
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
+        tx = tm.getTransaction();
+        assertNotNull(tx);
+        assertEquals(Status.STATUS_ACTIVE, tx.getStatus());
+
+        tx = tm.suspend();
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        assertNull(tm.getTransaction());
+
+        tm.resume(tx);
+        assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
+        assertEquals(tx, tm.getTransaction());
+
+        tm.rollback();
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        assertNull(tm.getTransaction());
+    }
+
+    public void testOneResource() throws Exception {
+        Transaction tx;
+        MockResource res1 = rm1.getResource("rm1");
+        tm.begin();
+        tx = tm.getTransaction();
+        assertNull(res1.getCurrentXid());
+        assertTrue(tx.enlistResource(res1));
+        assertNotNull(res1.getCurrentXid());
+        assertTrue(tx.delistResource(res1, XAResource.TMFAIL));
+        assertNull(res1.getCurrentXid());
+        tm.rollback();
+
+        tm.begin();
+        tx = tm.getTransaction();
+        assertTrue(tx.enlistResource(res1));
+        tm.rollback();
+        assertNull(res1.getCurrentXid());
+    }
+
+    protected void setUp() throws Exception {
+        tm = new TransactionManagerImpl();
+        rm1 = new MockResourceManager(true);
+        rm2 = new MockResourceManager(true);
+        rm3 = new MockResourceManager(false);
+    }
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/TransactionManagerImplTest.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/TransactionManagerImplTest.java
new file mode 100644
index 0000000..5f15a81
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/TransactionManagerImplTest.java
@@ -0,0 +1,317 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.util.Map;
+
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.Transaction;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class TransactionManagerImplTest extends TestCase {
+
+    MockResourceManager rm1 = new MockResourceManager(true);
+    MockResource r1_1 = rm1.getResource("rm1_1");
+    MockResource r1_2 = rm1.getResource("rm1_2");
+    MockResourceManager rm2 = new MockResourceManager(true);
+    MockResource r2_1 = rm2.getResource("rm2_1");
+    MockResource r2_2 = rm2.getResource("rm2_2");
+
+    TransactionLog transactionLog = new MockLog();
+
+    TransactionManagerImpl tm;
+
+    protected void setUp() throws Exception {
+        tm = new TransactionManagerImpl(10,
+                new XidFactoryImpl("WHAT DO WE CALL IT?".getBytes()), transactionLog);
+    }
+
+    protected void tearDown() throws Exception {
+        tm = null;
+    }
+
+    public void testNoResourcesCommit() throws Exception {
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
+        tm.commit();
+        assertNull(tm.getTransaction());
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
+        Transaction tx = tm.getTransaction();
+        assertNotNull(tx);
+        tx.commit();
+        assertNotNull(tm.getTransaction());
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+    }
+
+    public void testNoResourcesMarkRollbackOnly() throws Exception {
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
+        tm.getTransaction().setRollbackOnly();
+        assertEquals(Status.STATUS_MARKED_ROLLBACK, tm.getStatus());
+        try {
+            tm.commit();
+            fail("tx should not commit");
+        } catch (RollbackException e) {
+            //expected
+        }
+        assertNull(tm.getTransaction());
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
+        Transaction tx = tm.getTransaction();
+        assertNotNull(tx);
+        tx.setRollbackOnly();
+        assertEquals(Status.STATUS_MARKED_ROLLBACK, tx.getStatus());
+        try {
+            tx.commit();
+            fail("tx should not commit");
+        } catch (RollbackException e) {
+            //expected
+        }
+        assertNotNull(tm.getTransaction());
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+    }
+
+    public void testNoResourcesRollback() throws Exception {
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
+        tm.rollback();
+        assertNull(tm.getTransaction());
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
+        Transaction tx = tm.getTransaction();
+        assertNotNull(tx);
+        tx.rollback();
+        assertNotNull(tm.getTransaction());
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+
+        //check rollback when marked rollback only
+        tm.begin();
+        assertEquals(Status.STATUS_ACTIVE, tm.getStatus());
+        tm.getTransaction().setRollbackOnly();
+        assertEquals(Status.STATUS_MARKED_ROLLBACK, tm.getStatus());
+        tm.rollback();
+        assertNull(tm.getTransaction());
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+    }
+
+    public void testOneResourceCommit() throws Exception {
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        Transaction tx = tm.getTransaction();
+        tx.enlistResource(r1_1);
+        tx.delistResource(r1_1, XAResource.TMSUCCESS);
+        tx.commit();
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        assertTrue(r1_1.isCommitted());
+        assertTrue(!r1_1.isPrepared());
+        assertTrue(!r1_1.isRolledback());
+    }
+
+    public void testOneResourceMarkedRollback() throws Exception {
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        Transaction tx = tm.getTransaction();
+        tx.enlistResource(r1_1);
+        tx.setRollbackOnly();
+        tx.delistResource(r1_1, XAResource.TMSUCCESS);
+        try {
+            tx.commit();
+            fail("tx should roll back");
+        } catch (RollbackException e) {
+            //expected
+        }
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        assertTrue(!r1_1.isCommitted());
+        assertTrue(!r1_1.isPrepared());
+        assertTrue(r1_1.isRolledback());
+    }
+
+    public void testOneResourceRollback() throws Exception {
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        Transaction tx = tm.getTransaction();
+        tx.enlistResource(r1_1);
+        tx.delistResource(r1_1, XAResource.TMSUCCESS);
+        tx.rollback();
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        assertTrue(!r1_1.isCommitted());
+        assertTrue(!r1_1.isPrepared());
+        assertTrue(r1_1.isRolledback());
+    }
+
+    public void testTwoResourceOneRMCommit() throws Exception {
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        Transaction tx = tm.getTransaction();
+        tx.enlistResource(r1_1);
+        tx.delistResource(r1_1, XAResource.TMSUCCESS);
+        tx.enlistResource(r1_2);
+        tx.delistResource(r1_2, XAResource.TMSUCCESS);
+        tx.commit();
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        assertTrue(r1_1.isCommitted() ^ r1_2.isCommitted());
+        assertTrue(!r1_1.isPrepared() & !r1_2.isPrepared());
+        assertTrue(!r1_1.isRolledback() & !r1_2.isRolledback());
+    }
+
+    public void testTwoResourceOneRMMarkRollback() throws Exception {
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        Transaction tx = tm.getTransaction();
+        tx.enlistResource(r1_1);
+        tx.delistResource(r1_1, XAResource.TMSUCCESS);
+        tx.enlistResource(r1_2);
+        tx.delistResource(r1_2, XAResource.TMSUCCESS);
+        tx.setRollbackOnly();
+        try {
+            tx.commit();
+            fail("tx should roll back");
+        } catch (RollbackException e) {
+            //expected
+        }
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        assertTrue(!r1_1.isCommitted() & !r1_2.isCommitted());
+        assertTrue(!r1_1.isPrepared() & !r1_2.isPrepared());
+        assertTrue(r1_1.isRolledback() ^ r1_2.isRolledback());
+    }
+
+    public void testTwoResourcesOneRMRollback() throws Exception {
+        tm.begin();
+        Transaction tx = tm.getTransaction();
+        tx.enlistResource(r1_1);
+        tx.delistResource(r1_1, XAResource.TMSUCCESS);
+        tx.enlistResource(r1_2);
+        tx.delistResource(r1_2, XAResource.TMSUCCESS);
+        tx.setRollbackOnly();
+        tx.rollback();
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        assertTrue(!r1_1.isCommitted() & !r1_2.isCommitted());
+        assertTrue(!r1_1.isPrepared() & !r1_2.isPrepared());
+        assertTrue(r1_1.isRolledback() ^ r1_2.isRolledback());
+    }
+
+    public void testFourResourceTwoRMCommit() throws Exception {
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        tm.begin();
+        Transaction tx = tm.getTransaction();
+        tx.enlistResource(r1_1);
+        tx.enlistResource(r1_2);
+        tx.enlistResource(r2_1);
+        tx.enlistResource(r2_2);
+        tx.delistResource(r1_1, XAResource.TMSUCCESS);
+        tx.delistResource(r1_2, XAResource.TMSUCCESS);
+        tx.delistResource(r2_1, XAResource.TMSUCCESS);
+        tx.delistResource(r2_2, XAResource.TMSUCCESS);
+        tx.commit();
+        assertEquals(Status.STATUS_NO_TRANSACTION, tm.getStatus());
+        assertTrue((r1_1.isCommitted() & r1_1.isPrepared()) ^ (r1_2.isCommitted() & r1_2.isPrepared()));
+        assertTrue(!r1_1.isRolledback() & !r1_2.isRolledback());
+        assertTrue((r2_1.isCommitted() & r2_1.isPrepared()) ^ (r2_2.isCommitted() & r2_2.isPrepared()));
+        assertTrue(!r2_1.isRolledback() & !r2_2.isRolledback());
+    }
+
+    //BE VERY CAREFUL!! the ResourceManager only "recovers" the LAST resource it creates.
+    //This test depends on using the resource that will be recovered by the resource manager.
+    public void testSimpleRecovery() throws Exception {
+        //create a transaction in our own transaction manager
+        Xid xid = tm.xidFactory.createXid();
+        Transaction tx = tm.importXid(xid, 0);
+        tm.resume(tx);
+        assertSame(tx, tm.getTransaction());
+        tx.enlistResource(r1_2);
+        tx.enlistResource(r2_2);
+        tx.delistResource(r1_2, XAResource.TMSUCCESS);
+        tx.delistResource(r2_2, XAResource.TMSUCCESS);
+        tm.suspend();
+        tm.prepare(tx);
+        //recover
+        tm.recovery.recoverLog();
+        rm1.doRecovery(tm);
+        assertTrue(r1_2.isCommitted());
+        assertTrue(!r2_2.isCommitted());
+        rm2.doRecovery(tm);
+        assertTrue(r2_2.isCommitted());
+        assertTrue(tm.recovery.localRecoveryComplete());
+    }
+
+    public void testImportedXidRecovery() throws Exception {
+        //create a transaction from an external transaction manager.
+        XidFactory xidFactory2 = new XidFactoryImpl("tm2".getBytes());
+        Xid xid = xidFactory2.createXid();
+        Transaction tx = tm.importXid(xid, 0);
+        tm.resume(tx);
+        assertSame(tx, tm.getTransaction());
+        tx.enlistResource(r1_2);
+        tx.enlistResource(r2_2);
+        tx.delistResource(r1_2, XAResource.TMSUCCESS);
+        tx.delistResource(r2_2, XAResource.TMSUCCESS);
+        tm.suspend();
+        tm.prepare(tx);
+        //recover
+        tm.recovery.recoverLog();
+        rm1.doRecovery(tm);
+        assertTrue(!r1_2.isCommitted());
+        assertTrue(!r2_2.isCommitted());
+        rm2.doRecovery(tm);
+        assertTrue(!r2_2.isCommitted());
+        //there are no transactions started here, so local recovery is complete
+        assertTrue(tm.recovery.localRecoveryComplete());
+        Map recovered = tm.getExternalXids();
+        assertEquals(1, recovered.size());
+        assertEquals(xid, recovered.keySet().iterator().next());
+    }
+
+      public void testTimeout() throws Exception
+      {
+          long timeout = tm.getTransactionTimeoutMilliseconds(0L);
+          tm.setTransactionTimeout((int)timeout/4000);
+          tm.begin();
+          System.out.println("Test to sleep for " + timeout + " millisecs");
+          Thread.sleep(timeout);
+          try
+          {
+              tm.commit();
+              fail("Tx Should get Rollback exception");
+          }catch(RollbackException rex)
+          {
+              // Caught expected exception
+          }
+
+          // Now test if the default timeout is active
+          tm.begin();
+          System.out.println("Test to sleep for " + (timeout/2) + " millisecs");
+          Thread.sleep((timeout/2));
+          tm.commit();
+          // Its a failure if exception occurs.
+      }
+
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/TransactionSynchronizationRegistryTest.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/TransactionSynchronizationRegistryTest.java
new file mode 100644
index 0000000..0b311a5
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/TransactionSynchronizationRegistryTest.java
@@ -0,0 +1,162 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import javax.transaction.Synchronization;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.NotSupportedException;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class TransactionSynchronizationRegistryTest extends TestCase {
+
+    private static int beforeCounter = 0;
+    private static int afterCounter = 0;
+
+
+
+    private GeronimoTransactionManager tm;
+
+    private CountingSync interposedSync;
+    private CountingSync normalSync;
+
+    protected void setUp() throws Exception {
+        tm  = new GeronimoTransactionManager();
+    }
+
+    private void setUpInterposedSync() throws NotSupportedException, SystemException {
+        interposedSync = new CountingSync();
+        tm.begin();
+        tm.registerInterposedSynchronization(interposedSync);
+    }
+
+    private void setUpSyncs() throws Exception {
+        normalSync = new CountingSync();
+        setUpInterposedSync();
+        tm.getTransaction().registerSynchronization(normalSync);
+    }
+
+
+    public void testInterposedSynchIsCalledOnCommit() throws Exception {
+        setUpInterposedSync();
+        tm.commit();
+        checkInterposedSyncCalled();
+    }
+
+    private void checkInterposedSyncCalled() {
+        assertTrue("interposedSync beforeCompletion was not called", interposedSync.getBeforeCount() != -1);
+        assertTrue("interposedSync afterCompletion was not called", interposedSync.getAfterCount() != -1);
+    }
+
+    public void testInterposedSynchIsCalledOnRollback() throws Exception {
+        setUpInterposedSync();
+        tm.rollback();
+        checkInterposedSyncCalled();
+    }
+
+    public void testInterposedSynchIsCalledOnMarkRollback() throws Exception {
+        setUpInterposedSync();
+        tm.setRollbackOnly();
+        try {
+            tm.commit();
+            fail("expected a RollbackException");
+        } catch (HeuristicMixedException e) {
+            fail("expected a RollbackException not " + e.getClass());
+        } catch (HeuristicRollbackException e) {
+            fail("expected a RollbackException not " + e.getClass());
+        } catch (IllegalStateException e) {
+            fail("expected a RollbackException not " + e.getClass());
+        } catch (RollbackException e) {
+
+        } catch (SecurityException e) {
+            fail("expected a RollbackException not " + e.getClass());
+        } catch (SystemException e) {
+            fail("expected a RollbackException not " + e.getClass());
+        }
+        checkInterposedSyncCalled();
+    }
+
+    public void testSynchCallOrderOnCommit() throws Exception {
+        setUpSyncs();
+        tm.commit();
+        checkSyncCallOrder();
+    }
+
+    private void checkSyncCallOrder() {
+        checkInterposedSyncCalled();
+        assertTrue("interposedSync beforeCompletion was not called after normalSync beforeCompletion", interposedSync.getBeforeCount() > normalSync.getBeforeCount());
+        assertTrue("interposedSync afterCompletion was not called before normalSync beforeCompletion", interposedSync.getAfterCount() < normalSync.getAfterCount());
+    }
+
+    public void testSynchCallOrderOnRollback() throws Exception {
+        setUpSyncs();
+        tm.rollback();
+        checkSyncCallOrder();
+    }
+
+    public void testSynchCallOrderOnMarkRollback() throws Exception {
+        setUpSyncs();
+        tm.setRollbackOnly();
+        try {
+            tm.commit();
+            fail("expected a RollbackException");
+        } catch (HeuristicMixedException e) {
+            fail("expected a RollbackException not " + e.getClass());
+        } catch (HeuristicRollbackException e) {
+            fail("expected a RollbackException not " + e.getClass());
+        } catch (IllegalStateException e) {
+            fail("expected a RollbackException not " + e.getClass());
+        } catch (RollbackException e) {
+
+        } catch (SecurityException e) {
+            fail("expected a RollbackException not " + e.getClass());
+        } catch (SystemException e) {
+            fail("expected a RollbackException not " + e.getClass());
+        }
+        checkSyncCallOrder();
+    }
+
+    private class CountingSync implements Synchronization {
+
+        private int beforeCount = -1;
+        private int afterCount = -1;
+
+        public void beforeCompletion() {
+            beforeCount = beforeCounter++;
+        }
+
+        public void afterCompletion(int i) {
+            afterCount = afterCounter++;
+        }
+
+        public int getBeforeCount() {
+            return beforeCount;
+        }
+
+        public int getAfterCount() {
+            return afterCount;
+        }
+    }
+
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/XATransactionTester.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/XATransactionTester.java
new file mode 100644
index 0000000..65d63f9
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/XATransactionTester.java
@@ -0,0 +1,132 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import java.sql.Connection;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.sql.XAConnection;
+import javax.sql.XADataSource;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/**
+ *
+ *
+ *
+ * @version $Rev$ $Date$
+ */
+public class XATransactionTester {
+    private TransactionManager manager;
+    private XADataSource ds;
+    private Xid xid;
+
+    public static void main(String[] args) throws Exception {
+        new XATransactionTester().run(args);
+    }
+
+    public void run(String[] args) throws Exception {
+        ds = getDataSource(args);
+        XAConnection xaConn = ds.getXAConnection("test", "test");
+        XAResource xaRes = xaConn.getXAResource();
+        manager = new TransactionManagerImpl(10, new DummyLog());
+        Connection c = xaConn.getConnection();
+        Statement s = c.createStatement();
+
+        manager.begin();
+        manager.getTransaction().enlistResource(xaRes);
+        s.execute("UPDATE XA_TEST SET X=X+1");
+        manager.getTransaction().delistResource(xaRes, XAResource.TMSUCCESS);
+        manager.commit();
+
+/*
+        manager.begin();
+        manager.getTransaction().enlistResource(xaRes);
+        xid = new XidImpl(xid, 1);
+        System.out.println("xid = " + xid);
+        s.execute("UPDATE XA_TEST SET X=X+1");
+
+        xaRes.end(xid, XAResource.TMSUCCESS);
+        xaRes.prepare(xid);
+        c.close();
+*/
+
+/*
+        Xid[] prepared = xaRes.recover(XAResource.TMNOFLAGS);
+        for (int i = 0; i < prepared.length; i++) {
+            Xid xid = prepared[i];
+            StringBuffer s = new StringBuffer();
+            s.append(Integer.toHexString(xid.getFormatId())).append('.');
+            byte[] globalId = xid.getGlobalTransactionId();
+            for (int j = 0; j < globalId.length; j++) {
+                s.append(Integer.toHexString(globalId[j]));
+            }
+
+            System.out.println("recovery = " + s);
+            xaRes.forget(xid);
+        }
+*/
+
+    }
+
+    /*
+     * @todo get something that loads this from a file
+     */
+    private XADataSource getDataSource(String[] args) throws Exception {
+//        oracle.jdbc.xa.client.OracleXADataSource ds = new oracle.jdbc.xa.client.OracleXADataSource();
+//        ds.setConnectionURL("jdbc:oracle:thin:@localhost:1521:ABU");
+//        return ds;
+        return null;
+    }
+
+    private class DummyLog implements TransactionLog {
+
+        public void begin(Xid xid) throws LogException {
+            XATransactionTester.this.xid = xid;
+        }
+
+        public Object prepare(Xid xid, List branches) throws LogException {
+            return new Object();
+        }
+
+        public void commit(Xid xid, Object logMark) throws LogException {
+        }
+
+        public void rollback(Xid xid, Object logMark) throws LogException {
+        }
+
+        public Collection recover(XidFactory xidFactory) throws LogException {
+            return new ArrayList();
+        }
+
+        public String getXMLStats() {
+            return null;
+        }
+
+        public int getAverageForceTime() {
+            return 0;
+        }
+
+        public int getAverageBytesPerForce() {
+            return 0;
+        }
+    }
+}
diff --git a/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/XidImporterTest.java b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/XidImporterTest.java
new file mode 100644
index 0000000..8f447bb
--- /dev/null
+++ b/geronimo-transaction/src/test/java/org/apache/geronimo/transaction/manager/XidImporterTest.java
@@ -0,0 +1,141 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.
+ */
+
+package org.apache.geronimo.transaction.manager;
+
+import javax.transaction.xa.Xid;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.XAException;
+import javax.transaction.Transaction;
+import javax.transaction.Status;
+
+import junit.framework.TestCase;
+
+/**
+ *
+ *
+ * @version $Rev$ $Date$
+ *
+ * */
+public class XidImporterTest extends TestCase{
+
+    MockResourceManager rm1 = new MockResourceManager(true);
+    MockResource r1_1 = new MockResource(rm1, "rm1");
+    MockResource r1_2 = new MockResource(rm1, "rm1");
+    MockResourceManager rm2 = new MockResourceManager(true);
+    MockResource r2_1 = new MockResource(rm2, "rm2");
+    MockResource r2_2 = new MockResource(rm2, "rm2");
+
+    XidImporter tm;
+    XidFactory xidFactory = new XidFactoryImpl();
+
+    protected void setUp() throws Exception {
+        tm = new TransactionManagerImpl();
+    }
+
+    public void testImportXid() throws Exception {
+        Xid externalXid = xidFactory.createXid();
+        Transaction tx = tm.importXid(externalXid, 0);
+        assertNotNull(tx);
+        assertEquals(Status.STATUS_ACTIVE, tx.getStatus());
+    }
+
+    public void testNoResourcesCommitOnePhase() throws Exception {
+        Xid externalXid = xidFactory.createXid();
+        Transaction tx = tm.importXid(externalXid, 0);
+        tm.commit(tx, true);
+        assertEquals(Status.STATUS_NO_TRANSACTION, tx.getStatus());
+    }
+
+    public void testNoResourcesCommitTwoPhase() throws Exception {
+        Xid externalXid = xidFactory.createXid();
+        Transaction tx = tm.importXid(externalXid, 0);
+        assertEquals(XAResource.XA_RDONLY, tm.prepare(tx));
+        assertEquals(Status.STATUS_NO_TRANSACTION, tx.getStatus());
+    }
+
+    public void testNoResourcesMarkRollback() throws Exception {
+        Xid externalXid = xidFactory.createXid();
+        Transaction tx = tm.importXid(externalXid, 0);
+        tx.setRollbackOnly();
+        try {
+            tm.prepare(tx);
+            fail("should throw rollback exception in an XAException");
+        } catch (XAException e) {
+        }
+        assertEquals(Status.STATUS_NO_TRANSACTION, tx.getStatus());
+    }
+
+    public void testNoResourcesRollback() throws Exception {
+        Xid externalXid = xidFactory.createXid();
+        Transaction tx = tm.importXid(externalXid, 0);
+        tm.rollback(tx);
+        assertEquals(Status.STATUS_NO_TRANSACTION, tx.getStatus());
+    }
+
+    public void testOneResourceOnePhaseCommit() throws Exception {
+        Xid externalXid = xidFactory.createXid();
+        Transaction tx = tm.importXid(externalXid, 0);
+        tx.enlistResource(r1_1);
+        tx.delistResource(r1_1, XAResource.TMSUCCESS);
+        tm.commit(tx, true);
+        assertEquals(Status.STATUS_NO_TRANSACTION, tx.getStatus());
+    }
+
+    public void testOneResourceTwoPhaseCommit() throws Exception {
+        Xid externalXid = xidFactory.createXid();
+        Transaction tx = tm.importXid(externalXid, 0);
+        tx.enlistResource(r1_1);
+        tx.delistResource(r1_1, XAResource.TMSUCCESS);
+        assertEquals(XAResource.XA_OK, tm.prepare(tx));
+        assertTrue(!r1_1.isCommitted());
+        assertTrue(r1_1.isPrepared());
+        assertTrue(!r1_1.isRolledback());
+        tm.commit(tx, false);
+        assertEquals(Status.STATUS_NO_TRANSACTION, tx.getStatus());
+        assertTrue(r1_1.isCommitted());
+        assertTrue(r1_1.isPrepared());
+        assertTrue(!r1_1.isRolledback());
+    }
+
+    public void testFourResourceTwoPhaseCommit() throws Exception {
+        Xid externalXid = xidFactory.createXid();
+        Transaction tx = tm.importXid(externalXid, 0);
+        tx.enlistResource(r1_1);
+        tx.enlistResource(r1_2);
+        tx.enlistResource(r2_1);
+        tx.enlistResource(r2_2);
+        tx.delistResource(r1_1, XAResource.TMSUCCESS);
+        tx.delistResource(r1_2, XAResource.TMSUCCESS);
+        tx.delistResource(r2_1, XAResource.TMSUCCESS);
+        tx.delistResource(r2_2, XAResource.TMSUCCESS);
+        assertEquals(XAResource.XA_OK, tm.prepare(tx));
+        assertTrue(!r1_1.isCommitted() & !r1_2.isCommitted());
+        assertTrue(r1_1.isPrepared() ^ r1_2.isPrepared());
+        assertTrue(!r1_1.isRolledback() & !r1_2.isRolledback());
+        assertTrue(!r2_1.isCommitted() & !r2_2.isCommitted());
+        assertTrue(r2_1.isPrepared() ^ r2_2.isPrepared());
+        assertTrue(!r2_1.isRolledback() & !r2_2.isRolledback());
+        tm.commit(tx, false);
+        assertEquals(Status.STATUS_NO_TRANSACTION, tx.getStatus());
+        assertTrue((r1_1.isCommitted() & r1_1.isPrepared()) ^ (r1_2.isCommitted() & r1_2.isPrepared()));
+        assertTrue(!r1_1.isRolledback() & !r1_2.isRolledback());
+        assertTrue((r2_1.isCommitted() & r2_1.isPrepared()) ^ (r2_2.isCommitted() & r2_2.isPrepared()));
+        assertTrue(!r2_1.isRolledback() & !r2_2.isRolledback());
+    }
+
+}