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());
+ }
+
+}