Commit the Transaction Service (Felix-1012) donated by Guillaume Nodet
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@763278 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/transaction/integration-test/pom.xml b/transaction/integration-test/pom.xml
new file mode 100644
index 0000000..c16b43b
--- /dev/null
+++ b/transaction/integration-test/pom.xml
@@ -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.
+-->
+<project>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>transaction</artifactId>
+ <version>0.9.0-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>org.apache.felix.transaction.itests</artifactId>
+ <groupId>org.apache.felix</groupId>
+ <name>Apache Felix Transaction iTests</name>
+ <version>0.9.0-SNAPSHOT</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.servicemix.kernel.testing</groupId>
+ <artifactId>org.apache.servicemix.kernel.testing.support</artifactId>
+ <version>1.1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.transaction</artifactId>
+ <version>${pom.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-tx</artifactId>
+ <version>2.5.6</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.servicemix.tooling</groupId>
+ <artifactId>depends-maven-plugin</artifactId>
+ <version>1.1</version>
+ <executions>
+ <execution>
+ <id>generate-depends-file</id>
+ <goals>
+ <goal>generate-depends-file</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/transaction/integration-test/src/test/java/org/apache/felix/transaction/itests/Test.java b/transaction/integration-test/src/test/java/org/apache/felix/transaction/itests/Test.java
new file mode 100644
index 0000000..5669efa
--- /dev/null
+++ b/transaction/integration-test/src/test/java/org/apache/felix/transaction/itests/Test.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.transaction.itests;
+
+import javax.transaction.TransactionManager;
+
+import org.apache.servicemix.kernel.testing.support.AbstractIntegrationTest;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Bundle;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+/**
+ */
+public class Test extends AbstractIntegrationTest {
+
+ private static final String PLATFORM_TRANSACTION_MANAGER_CLASS = "org.springframework.transaction.PlatformTransactionManager";
+
+ public void test() throws Exception {
+ ServiceTracker tracker = new ServiceTracker(bundleContext, TransactionManager.class.getName(), null);
+ tracker.open();
+
+ tracker.waitForService(5000L);
+ ServiceReference ref = tracker.getServiceReference();
+ assertNotNull(ref);
+ String[] objClass = (String[]) ref.getProperty(Constants.OBJECTCLASS);
+ assertNotNull(objClass);
+ boolean found = false;
+ for (String clazz : objClass) {
+ found |= PLATFORM_TRANSACTION_MANAGER_CLASS.equals(clazz);
+ }
+ assertFalse(found);
+
+ Bundle bundle = ref.getBundle();
+ bundle.stop();
+ installBundle("org.springframework", "spring-tx", null, "jar");
+ getOsgiService(PackageAdmin.class).refreshPackages(new Bundle[] { bundle });
+ System.err.println("Bundle refreshed");
+ Thread.sleep(500);
+ System.err.println("Starting bundle");
+ bundle.start();
+
+ tracker.waitForService(5000L);
+ ref = tracker.getServiceReference();
+ assertNotNull(ref);
+ objClass = (String[]) ref.getProperty(Constants.OBJECTCLASS);
+ assertNotNull(objClass);
+ found = false;
+ for (String clazz : objClass) {
+ found |= PLATFORM_TRANSACTION_MANAGER_CLASS.equals(clazz);
+ }
+ assertTrue(found);
+
+ tracker.close();
+ }
+
+ //============= Plumbing ==============/
+
+ /**
+ * The manifest to use for the "virtual bundle" created
+ * out of the test classes and resources in this project
+ *
+ * This is actually the boilerplate manifest with one additional
+ * import-package added. We should provide a simpler customization
+ * point for such use cases that doesn't require duplication
+ * of the entire manifest...
+ */
+ protected String getManifestLocation() {
+ return "classpath:org/apache/felix/transaction/MANIFEST.MF";
+ }
+
+ /**
+ * The location of the packaged OSGi bundles to be installed
+ * for this test. Values are Spring resource paths. The bundles
+ * we want to use are part of the same multi-project maven
+ * build as this project is. Hence we use the localMavenArtifact
+ * helper method to find the bundles produced by the package
+ * phase of the maven build (these tests will run after the
+ * packaging phase, in the integration-test phase).
+ *
+ * JUnit, commons-logging, spring-core and the spring OSGi
+ * test bundle are automatically included so do not need
+ * to be specified here.
+ */
+ protected String[] getTestBundlesNames() {
+ return new String[] {
+ getBundle("org.apache.geronimo.specs", "geronimo-jta_1.1_spec"),
+ getBundle("org.apache.geronimo.specs", "geronimo-j2ee-connector_1.5_spec"),
+ getBundle("org.apache.felix", "org.apache.felix.transaction"),
+ };
+ }
+
+}
\ No newline at end of file
diff --git a/transaction/integration-test/src/test/resources/org/apache/felix/transaction/MANIFEST.MF b/transaction/integration-test/src/test/resources/org/apache/felix/transaction/MANIFEST.MF
new file mode 100644
index 0000000..27229b8
--- /dev/null
+++ b/transaction/integration-test/src/test/resources/org/apache/felix/transaction/MANIFEST.MF
@@ -0,0 +1,30 @@
+Manifest-Version: 1.0
+License-00: .
+License-01: Licensed to the Apache Software Foundation (ASF) under one or more
+License-02: contributor license agreements. See the NOTICE file distributed with
+License-03: this work for additional information regarding copyright ownership.
+License-04: The ASF licenses this file to You under the Apache License, Version 2.0
+License-05: (the "License"); you may not use this file except in compliance with
+License-06: the License. You may obtain a copy of the License at
+License-07: .
+License-08: http://www.apache.org/licenses/LICENSE-2.0
+License-09: .
+License-10: Unless required by applicable law or agreed to in writing, software
+License-11: distributed under the License is distributed on an "AS IS" BASIS,
+License-12: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+License-13: See the License for the specific language governing permissions and
+License-14: limitations under the License.
+License-15: .
+Bundle-Name: org.apache.servicemix.testing.itests
+Bundle-SymbolicName: org.apache.servicemix.testing.itests
+Bundle-Vendor: Spring Framework
+Bundle-Activator: org.springframework.osgi.test.JUnitTestActivator
+Import-Package: junit.framework,
+ org.osgi.framework;specification-version="1.3.0",
+ org.apache.commons.logging,
+ org.springframework.core.io,
+ org.springframework.osgi.test,
+ org.apache.servicemix.kernel.testing.support,
+ org.springframework.osgi.util,
+ javax.transaction
+DynamicImport-Package: *
diff --git a/transaction/org.apache.felix.transaction/pom.xml b/transaction/org.apache.felix.transaction/pom.xml
new file mode 100644
index 0000000..955627b
--- /dev/null
+++ b/transaction/org.apache.felix.transaction/pom.xml
@@ -0,0 +1,225 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<project>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>transaction</artifactId>
+ <version>0.9.0-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>org.apache.felix.transaction</artifactId>
+ <groupId>org.apache.felix</groupId>
+ <name>Apache Felix Transaction</name>
+ <version>0.9.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <properties>
+ <geronimo-transaction-version>2.1.2</geronimo-transaction-version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>1.2.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>1.2.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.components</groupId>
+ <artifactId>geronimo-transaction</artifactId>
+ <version>2.1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.objectweb.howl</groupId>
+ <artifactId>howl</artifactId>
+ <version>1.0.1-1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-tx</artifactId>
+ <version>2.5.6</version>
+ <optional>true</optional>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>2.0.0</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ org.springframework.*;resolution:=optional,
+ *
+ </Import-Package>
+ <Export-Package>
+ org.apache.geronimo.transaction*;version="${geronimo-transaction-version}"
+ </Export-Package>
+ <Private-Package>
+ org.objectweb.howl.log*,
+ org.apache.felix.transaction.internal
+ </Private-Package>
+ <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
+ <Bundle-Activator>org.apache.felix.transaction.internal.Activator</Bundle-Activator>
+ <Export-Service>
+ javax.transaction.TransactionManager,
+ javax.transaction.TransactionSynchronizationRegistry,
+ javax.transaction.UserTransaction,
+ org.apache.geronimo.transaction.manager.RecoverableTransactionManager
+ </Export-Service>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>unpack-sources</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.apache.geronimo.components</groupId>
+ <artifactId>geronimo-transaction</artifactId>
+ <classifier>sources</classifier>
+ </artifactItem>
+ <artifactItem>
+ <groupId>org.objectweb.howl</groupId>
+ <artifactId>howl</artifactId>
+ <classifier>sources</classifier>
+ </artifactItem>
+ </artifactItems>
+ <outputDirectory>${project.build.directory}/sources</outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>generate-sources</phase>
+ <configuration>
+ <tasks>
+ <copy todir="${project.build.directory}/sources">
+ <fileset dir="${basedir}/src/main/java" />
+ </copy>
+ </tasks>
+ </configuration>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <phase>package</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>package1</id>
+ <phase>package</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ <configuration>
+ <minmemory>128m</minmemory>
+ <maxmemory>512m</maxmemory>
+ <sourcepath>${project.build.directory}/sources</sourcepath>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>1.1</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <artifactSet>
+ <includes>
+ <include>org.apache.geronimo.components:geronimo-transaction</include>
+ <include>org.objectweb.howl:howl</include>
+ <include>${project.groupId}:${project.artifactId}</include>
+ </includes>
+ </artifactSet>
+ <filters>
+ <filter>
+ <artifact>org.apache.geronimo.components:geronimo-transaction</artifact>
+ <excludes>
+ <exclude>org/**</exclude>
+ </excludes>
+ </filter>
+ <filter>
+ <artifact>org.objectweb.howl:howl</artifact>
+ <excludes>
+ <exclude>org/**</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ <createSourcesJar>true</createSourcesJar>
+ <promoteTransitiveDependencies>true</promoteTransitiveDependencies>
+ <createDependencyReducedPom>true</createDependencyReducedPom>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/transaction/org.apache.felix.transaction/src/main/java/org/apache/felix/transaction/internal/Activator.java b/transaction/org.apache.felix.transaction/src/main/java/org/apache/felix/transaction/internal/Activator.java
new file mode 100644
index 0000000..4bff950
--- /dev/null
+++ b/transaction/org.apache.felix.transaction/src/main/java/org/apache/felix/transaction/internal/Activator.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.transaction.internal;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Iterator;
+import java.util.HashMap;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.cm.ConfigurationException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ */
+public class Activator implements BundleActivator, ManagedServiceFactory {
+
+ private static final Logger log = LoggerFactory.getLogger("org.apache.felix.transaction");
+
+ private BundleContext bundleContext;
+ private Map managers = new HashMap<String, TransactionManagerService>();
+
+ public void start(BundleContext bundleContext) throws Exception {
+ this.bundleContext = bundleContext;
+ Hashtable props = new Hashtable();
+ props.put(Constants.SERVICE_PID, getName());
+ bundleContext.registerService(ManagedServiceFactory.class.getName(), this, props);
+
+ Hashtable ht = new Hashtable();
+ updated("initial", ht);
+ }
+
+ private void set(Hashtable ht, String key) {
+ String o = bundleContext.getProperty(key);
+ if (o == null) {
+ o = System.getenv(key.toUpperCase().replaceAll(".", "_"));
+ if (o == null) {
+ return;
+ }
+ }
+ ht.put(key, o);
+ }
+
+ public void stop(BundleContext context) throws Exception {
+ for (Iterator w = managers.values().iterator(); w.hasNext();) {
+ try {
+ TransactionManagerService mgr = (TransactionManagerService) w.next();
+ w.remove();
+ mgr.close();
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+ }
+
+ public String getName() {
+ return "org.apache.felix.transaction";
+ }
+
+ public void updated(String pid, Dictionary properties) throws ConfigurationException {
+ deleted(pid);
+ TransactionManagerService mgr = new TransactionManagerService(pid, properties, bundleContext);
+ managers.put(pid, mgr);
+ try {
+ mgr.start();
+ } catch (Exception e) {
+ log.error("Error starting transaction manager", e);
+ }
+ }
+
+ public void deleted(String pid) {
+ TransactionManagerService mgr = (TransactionManagerService) managers.remove(pid);
+ if (mgr != null) {
+ try {
+ mgr.close();
+ } catch (Exception e) {
+ log.error("Error stopping transaction manager", e);
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/transaction/org.apache.felix.transaction/src/main/java/org/apache/felix/transaction/internal/GeronimoPlatformTransactionManager.java b/transaction/org.apache.felix.transaction/src/main/java/org/apache/felix/transaction/internal/GeronimoPlatformTransactionManager.java
new file mode 100644
index 0000000..77bde50
--- /dev/null
+++ b/transaction/org.apache.felix.transaction/src/main/java/org/apache/felix/transaction/internal/GeronimoPlatformTransactionManager.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.transaction.internal;
+
+import java.util.Map;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.transaction.Transaction;
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import javax.transaction.xa.XAException;
+
+import org.apache.geronimo.transaction.manager.GeronimoTransactionManager;
+import org.apache.geronimo.transaction.manager.TransactionLog;
+import org.apache.geronimo.transaction.manager.XidFactory;
+import org.apache.geronimo.transaction.manager.TransactionManagerMonitor;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.jta.JtaTransactionManager;
+
+/**
+ */
+public class GeronimoPlatformTransactionManager extends GeronimoTransactionManager implements PlatformTransactionManager {
+
+ private final PlatformTransactionManager platformTransactionManager;
+ private final Map<Transaction, SuspendedResourcesHolder> suspendedResources = new ConcurrentHashMap<Transaction, SuspendedResourcesHolder>();
+
+ public GeronimoPlatformTransactionManager() throws XAException {
+ platformTransactionManager = new JtaTransactionManager(this, this);
+ registerTransactionAssociationListener();
+ }
+
+ public GeronimoPlatformTransactionManager(int defaultTransactionTimeoutSeconds) throws XAException {
+ super(defaultTransactionTimeoutSeconds);
+ platformTransactionManager = new JtaTransactionManager(this, this);
+ registerTransactionAssociationListener();
+ }
+
+ public GeronimoPlatformTransactionManager(int defaultTransactionTimeoutSeconds, TransactionLog transactionLog) throws XAException {
+ super(defaultTransactionTimeoutSeconds, transactionLog);
+ platformTransactionManager = new JtaTransactionManager(this, this);
+ registerTransactionAssociationListener();
+ }
+
+ public GeronimoPlatformTransactionManager(int defaultTransactionTimeoutSeconds, XidFactory xidFactory, TransactionLog transactionLog) throws XAException {
+ super(defaultTransactionTimeoutSeconds, xidFactory, transactionLog);
+ platformTransactionManager = new JtaTransactionManager(this, this);
+ registerTransactionAssociationListener();
+ }
+
+ public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
+ return platformTransactionManager.getTransaction(definition);
+ }
+
+ public void commit(TransactionStatus status) throws TransactionException {
+ platformTransactionManager.commit(status);
+ }
+
+ public void rollback(TransactionStatus status) throws TransactionException {
+ platformTransactionManager.rollback(status);
+ }
+
+ protected void registerTransactionAssociationListener() {
+ addTransactionAssociationListener(new TransactionManagerMonitor() {
+ public void threadAssociated(Transaction transaction) {
+ try {
+ if (transaction.getStatus() == Status.STATUS_ACTIVE) {
+ SuspendedResourcesHolder holder = suspendedResources.remove(transaction);
+ if (holder != null && holder.getSuspendedSynchronizations() != null) {
+ TransactionSynchronizationManager.setActualTransactionActive(true);
+ TransactionSynchronizationManager.setCurrentTransactionReadOnly(holder.isReadOnly());
+ TransactionSynchronizationManager.setCurrentTransactionName(holder.getName());
+ TransactionSynchronizationManager.initSynchronization();
+ for (Iterator<?> it = holder.getSuspendedSynchronizations().iterator(); it.hasNext();) {
+ TransactionSynchronization synchronization = (TransactionSynchronization) it.next();
+ synchronization.resume();
+ TransactionSynchronizationManager.registerSynchronization(synchronization);
+ }
+ }
+ }
+ } catch (SystemException e) {
+ return;
+ }
+ }
+ public void threadUnassociated(Transaction transaction) {
+ try {
+ if (transaction.getStatus() == Status.STATUS_ACTIVE) {
+ if (TransactionSynchronizationManager.isSynchronizationActive()) {
+ List<?> suspendedSynchronizations = TransactionSynchronizationManager.getSynchronizations();
+ for (Iterator<?> it = suspendedSynchronizations.iterator(); it.hasNext();) {
+ ((TransactionSynchronization) it.next()).suspend();
+ }
+ TransactionSynchronizationManager.clearSynchronization();
+ String name = TransactionSynchronizationManager.getCurrentTransactionName();
+ TransactionSynchronizationManager.setCurrentTransactionName(null);
+ boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
+ TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
+ TransactionSynchronizationManager.setActualTransactionActive(false);
+ SuspendedResourcesHolder holder = new SuspendedResourcesHolder(null, suspendedSynchronizations, name, readOnly);
+ suspendedResources.put(transaction, holder);
+ }
+ }
+ } catch (SystemException e) {
+ return;
+ }
+ }
+ });
+ }
+
+ /**
+ * Holder for suspended resources.
+ * Used internally by <code>suspend</code> and <code>resume</code>.
+ */
+ private static class SuspendedResourcesHolder {
+
+ private final Object suspendedResources;
+
+ private final List<?> suspendedSynchronizations;
+
+ private final String name;
+
+ private final boolean readOnly;
+
+ public SuspendedResourcesHolder(
+ Object suspendedResources, List<?> suspendedSynchronizations, String name, boolean readOnly) {
+
+ this.suspendedResources = suspendedResources;
+ this.suspendedSynchronizations = suspendedSynchronizations;
+ this.name = name;
+ this.readOnly = readOnly;
+ }
+
+ public Object getSuspendedResources() {
+ return suspendedResources;
+ }
+
+ public List<?> getSuspendedSynchronizations() {
+ return suspendedSynchronizations;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+ }
+
+}
diff --git a/transaction/org.apache.felix.transaction/src/main/java/org/apache/felix/transaction/internal/TransactionManagerService.java b/transaction/org.apache.felix.transaction/src/main/java/org/apache/felix/transaction/internal/TransactionManagerService.java
new file mode 100644
index 0000000..22997b2
--- /dev/null
+++ b/transaction/org.apache.felix.transaction/src/main/java/org/apache/felix/transaction/internal/TransactionManagerService.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.felix.transaction.internal;
+
+import java.util.Dictionary;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Properties;
+import java.io.File;
+import java.io.IOException;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.TransactionManager;
+import javax.transaction.TransactionSynchronizationRegistry;
+import javax.transaction.UserTransaction;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
+import org.apache.geronimo.transaction.manager.GeronimoTransactionManager;
+import org.apache.geronimo.transaction.manager.TransactionLog;
+import org.apache.geronimo.transaction.manager.XidFactory;
+import org.apache.geronimo.transaction.manager.XidFactoryImpl;
+import org.apache.geronimo.transaction.manager.RecoverableTransactionManager;
+import org.apache.geronimo.transaction.log.HOWLLog;
+import org.apache.geronimo.transaction.log.UnrecoverableLog;
+import org.objectweb.howl.log.LogConfigurationException;
+
+/**
+ */
+public class TransactionManagerService {
+
+ public static final String TRANSACTION_TIMEOUT = "felix.transaction.timeout";
+ public static final String RECOVERABLE = "felix.transaction.recoverable";
+ public static final String HOWL_BUFFER_CLASS_NAME = "felix.transaction.howl.bufferClassName";
+ public static final String HOWL_BUFFER_SIZE = "felix.transaction.howl.bufferSize";
+ public static final String HOWL_CHECKSUM_ENABLED = "felix.transaction.howl.checksumEnabled";
+ public static final String HOWL_ADLER32_CHECKSUM = "felix.transaction.howl.adler32Checksum";
+ public static final String HOWL_FLUSH_SLEEP_TIME = "felix.transaction.howl.flushSleepTime";
+ public static final String HOWL_LOG_FILE_EXT = "felix.transaction.howl.logFileExt";
+ public static final String HOWL_LOG_FILE_NAME = "felix.transaction.howl.logFileName";
+ public static final String HOWL_MAX_BLOCKS_PER_FILE = "felix.transaction.howl.maxBlocksPerFile";
+ public static final String HOWL_MAX_LOG_FILES = "felix.transaction.howl.maxLogFiles";
+ public static final String HOWL_MAX_BUFFERS = "felix.transaction.howl.maxBuffers";
+ public static final String HOWL_MIN_BUFFERS = "felix.transaction.howl.minBuffers";
+ public static final String HOWL_THREADS_WAITING_FORCE_THRESHOLD = "felix.transaction.howl.threadsWaitingForceThreshold";
+ public static final String HOWL_LOG_FILE_DIR = "felix.transaction.howl.logFileDir";
+
+ public static final int DEFAULT_TRANSACTION_TIMEOUT = 600; // 600 seconds -> 10 minutes
+ public static final boolean DEFAULT_RECOVERABLE = false; // not recoverable by default
+
+ private static final String PLATFORM_TRANSACTION_MANAGER_CLASS = "org.springframework.transaction.PlatformTransactionManager";
+
+ private final String pid;
+ private final Dictionary properties;
+ private final BundleContext bundleContext;
+ private boolean useSpring;
+ private GeronimoTransactionManager transactionManager;
+ private TransactionLog transactionLog;
+ private ServiceRegistration serviceRegistration;
+
+ public TransactionManagerService(String pid, Dictionary properties, BundleContext bundleContext) throws ConfigurationException {
+ this.pid = pid;
+ this.properties = properties;
+ this.bundleContext = bundleContext;
+ // Transaction timeout
+ int transactionTimeout = getInt(TRANSACTION_TIMEOUT, DEFAULT_TRANSACTION_TIMEOUT);
+ if (transactionTimeout <= 0) {
+ throw new ConfigurationException(TRANSACTION_TIMEOUT, "Property " + TRANSACTION_TIMEOUT + " must be > 0");
+ }
+ // XID factory
+ XidFactory xidFactory = new XidFactoryImpl(pid.getBytes());
+ // Transaction log
+ if (getBool(RECOVERABLE, DEFAULT_RECOVERABLE)) {
+ String bufferClassName = getString(HOWL_BUFFER_CLASS_NAME, "org.objectweb.howl.log.BlockLogBuffer");
+ int bufferSizeKBytes = getInt(HOWL_BUFFER_SIZE, 32);
+ if (bufferSizeKBytes < 1 || bufferSizeKBytes > 32) {
+ throw new ConfigurationException(HOWL_BUFFER_SIZE, "bufferSize must be between 1 and 32");
+ }
+ boolean checksumEnabled = getBool(HOWL_CHECKSUM_ENABLED, true);
+ boolean adler32Checksum = getBool(HOWL_ADLER32_CHECKSUM, true);
+ int flushSleepTimeMilliseconds = getInt(HOWL_FLUSH_SLEEP_TIME, 50);
+ String logFileExt = getString(HOWL_LOG_FILE_EXT, "log");
+ String logFileName = getString(HOWL_LOG_FILE_NAME, "transaction");
+ int maxBlocksPerFile = getInt(HOWL_MAX_BLOCKS_PER_FILE, -1);
+ int maxLogFiles = getInt(HOWL_MAX_LOG_FILES, 2);
+ int minBuffers = getInt(HOWL_MIN_BUFFERS, 4);
+ if (minBuffers < 0) {
+ throw new ConfigurationException(HOWL_MIN_BUFFERS, "minBuffers must be > 0");
+ }
+ int maxBuffers = getInt(HOWL_MAX_BUFFERS, 0);
+ if (maxBuffers > 0 && minBuffers < maxBuffers) {
+ throw new ConfigurationException(HOWL_MAX_BUFFERS, "minBuffers must be <= maxBuffers");
+ }
+ int threadsWaitingForceThreshold = getInt(HOWL_THREADS_WAITING_FORCE_THRESHOLD, -1);
+ String logFileDir = getString(HOWL_LOG_FILE_DIR, null);
+ if (logFileDir == null || logFileDir.length() == 0 || !new File(logFileDir).isAbsolute()) {
+ throw new ConfigurationException(HOWL_LOG_FILE_DIR, "Property should be set to an absolute directory");
+ }
+ try {
+ transactionLog = new HOWLLog(bufferClassName,
+ bufferSizeKBytes,
+ checksumEnabled,
+ adler32Checksum,
+ flushSleepTimeMilliseconds,
+ logFileDir,
+ logFileExt,
+ logFileName,
+ maxBlocksPerFile,
+ maxBuffers,
+ maxLogFiles,
+ minBuffers,
+ threadsWaitingForceThreshold,
+ xidFactory != null ? xidFactory : new XidFactoryImpl(),
+ null);
+ } catch (LogConfigurationException e) {
+ // This should not really happen as we've checked properties earlier
+ throw new ConfigurationException(null, null, e);
+ } catch (IOException e) {
+ // This should not really happen as we've checked properties earlier
+ throw new ConfigurationException(null, null, e);
+ }
+ } else {
+ transactionLog = new UnrecoverableLog();
+ }
+ // Create transaction manager
+ try {
+ try {
+ transactionManager = new SpringTransactionManagerCreator().create(transactionTimeout, xidFactory, transactionLog);
+ useSpring = true;
+ } catch (NoClassDefFoundError e) {
+ transactionManager = new GeronimoTransactionManager(transactionTimeout, xidFactory, transactionLog);
+ }
+ } catch (XAException e) {
+ throw new RuntimeException("Error recovering transaction log", e);
+ }
+ }
+
+ public void start() throws Exception {
+ if (transactionLog instanceof HOWLLog) {
+ ((HOWLLog) transactionLog).doStart();
+ }
+ List<String> clazzes = new ArrayList<String>();
+ clazzes.add(TransactionManager.class.getName());
+ clazzes.add(TransactionSynchronizationRegistry.class.getName());
+ clazzes.add(UserTransaction.class.getName());
+ clazzes.add(RecoverableTransactionManager.class.getName());
+ if (useSpring) {
+ clazzes.add(PLATFORM_TRANSACTION_MANAGER_CLASS);
+ }
+ serviceRegistration = bundleContext.registerService(clazzes.toArray(new String[clazzes.size()]), transactionManager, new Properties());
+ }
+
+ public void close() throws Exception {
+ if (serviceRegistration != null) {
+ serviceRegistration.unregister();
+ }
+ if (transactionLog instanceof HOWLLog) {
+ ((HOWLLog) transactionLog).doStop();
+ }
+ }
+
+ private String getString(String property, String dflt) throws ConfigurationException {
+ String value = (String) properties.get(property);
+ if (value != null) {
+ return value;
+ }
+ return dflt;
+ }
+
+ private int getInt(String property, int dflt) throws ConfigurationException {
+ String value = (String) properties.get(property);
+ if (value != null) {
+ try {
+ return Integer.parseInt(value);
+ } catch (Exception e) {
+ throw new ConfigurationException(property, "Error parsing " + property + "(" + value + ") property as an integer", e);
+ }
+ }
+ return dflt;
+ }
+
+ private boolean getBool(String property, boolean dflt) throws ConfigurationException {
+ String value = (String) properties.get(property);
+ if (value != null) {
+ try {
+ return Boolean.parseBoolean(value);
+ } catch (Exception e) {
+ throw new ConfigurationException(property, "Error parsing " + property + "(" + value + ") property as a boolean", e);
+ }
+ }
+ return dflt;
+ }
+
+ /**
+ * We use an inner static class to decouple this class from the spring-tx classes
+ * in order to not have NoClassDefFoundError if those are not present.
+ */
+ public static class SpringTransactionManagerCreator {
+
+ public GeronimoTransactionManager create(int defaultTransactionTimeoutSeconds, XidFactory xidFactory, TransactionLog transactionLog) throws XAException {
+ return new GeronimoPlatformTransactionManager(defaultTransactionTimeoutSeconds, xidFactory, transactionLog);
+ }
+
+ }
+}
diff --git a/transaction/pom.xml b/transaction/pom.xml
new file mode 100644
index 0000000..bb4cc71
--- /dev/null
+++ b/transaction/pom.xml
@@ -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.
+-->
+<project>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix</artifactId>
+ <version>1.0.4</version>
+ <relativePath>../pom/pom.xml</relativePath>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>transaction</artifactId>
+ <groupId>org.apache.felix</groupId>
+ <name>Apache Felix Transaction Parent</name>
+ <version>0.9.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>org.apache.felix.transaction</module>
+ <module>integration-test</module>
+ </modules>
+
+</project>