With JOTM, the same object implements both the + * {@link javax.transaction.UserTransaction} and the + * {@link javax.transaction.TransactionManager} interface, + * as returned by this FactoryBean. + *
+ *A local JOTM instance is well-suited for working in conjunction with + * ObjectWeb's XAPool, e.g. with bean + * definitions like the following: + *
+ *+ * <bean id="jotm" class="org.springframework.transaction.jta.JotmFactoryBean"/> + * + * <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> + * <property name="userTransaction" ref="jotm"/> + * </bean> + * + * <bean id="innerDataSource" class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown"> + * <property name="transactionManager" ref="jotm"/> + * <property name="driverName" value="..."/> + * <property name="url" value="..."/> + * <property name="user" value="..."/> + * <property name="password" value="..."/> + * </bean> + * + * <bean id="dataSource" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown"> + * <property name="dataSource" ref="innerDataSource"/> + * <property name="user" value="..."/> + * <property name="password" value="..."/> + * <property name="maxSize" value="..."/> + * </bean>+ * + * Note that Spring's {@link org.springframework.transaction.jta.JtaTransactionManager} will automatically detect + * that the passed-in UserTransaction reference also implements the + * TransactionManager interface. Hence, it is not necessary to specify a + * separate reference for JtaTransactionManager's "transactionManager" property. + * + *
Implementation note: This FactoryBean uses JOTM's static access method + * to obtain the JOTM {@link org.objectweb.jotm.Current} object, which + * implements both the UserTransaction and the TransactionManager interface, + * as mentioned above. + * + * @author Juergen Hoeller + * @see org.springframework.transaction.jta.JtaTransactionManager#setUserTransaction + * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + * @see org.objectweb.jotm.Current + * @since 21.01.2004 + */ +public class JotmFactoryBean implements FactoryBean, DisposableBean, InitializingBean { + + private Current jotmCurrent; + + private Jotm jotm; + private String jotmHome; + private Integer defaultTimeout; + private Boolean transactionRecovery; + + + /** + * Set the default transaction timeout for the JOTM instance. + *
Should only be called for a local JOTM instance,
+ * not when accessing an existing (shared) JOTM instance.
+ */
+ public void setDefaultTimeout(int defaultTimeout) {
+ this.defaultTimeout = defaultTimeout;
+ }
+
+
+ public void setTransactionRecovery(boolean transactionRecovery) throws SystemException {
+ this.transactionRecovery = transactionRecovery;
+ }
+
+ /**
+ * @param jotmHome the directory that contains conf/jotm.properties
+ */
+ public void setJotmHome(String jotmHome) {
+ this.jotmHome = jotmHome;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ createJotm();
+ }
+
+ private void createJotm() throws NamingException, SystemException {
+ if (jotmHome!=null) {
+ System.setProperty("jotm.home",jotmHome);
+ }
+ // Check for already active JOTM instance.
+ this.jotmCurrent = Current.getCurrent();
+
+ // If none found, create new local JOTM instance.
+ if (this.jotmCurrent == null) {
+ // Only for use within the current Spring context:
+ // local, not bound to registry.
+ this.jotm = new Jotm(true, false);
+ this.jotmCurrent = Current.getCurrent();
+ }
+ if (defaultTimeout!=null) {
+ this.jotmCurrent.setDefaultTimeout(defaultTimeout);
+ }
+ if (transactionRecovery!=null) {
+ this.jotmCurrent.setTransactionRecovery(transactionRecovery);
+ }
+ }
+
+ /**
+ * Return the JOTM instance created by this factory bean, if any.
+ * Will be null if an already active JOTM instance is used.
+ *
Application code should never need to access this. + */ + public Jotm getJotm() { + return this.jotm; + } + + + public Object getObject() { + return this.jotmCurrent; + } + + public Class getObjectType() { + return this.jotmCurrent.getClass(); + } + + public boolean isSingleton() { + return true; + } + + + /** + * Stop the local JOTM instance, if created by this FactoryBean. + */ + public void destroy() { + if (this.jotm != null) { + this.jotm.stop(); + } + } + +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/MultiTransactionStatus.java b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/transaction/MultiTransactionStatus.java similarity index 98% rename from spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/MultiTransactionStatus.java rename to spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/transaction/MultiTransactionStatus.java index 4ecac2018..10306a1ee 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/MultiTransactionStatus.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/transaction/MultiTransactionStatus.java @@ -1,4 +1,4 @@ -package org.springframework.data.graph.neo4j.support; +package org.springframework.data.graph.neo4j.transaction; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/transaction/SpringProvider.java b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/transaction/SpringProvider.java new file mode 100644 index 000000000..cf51c57ad --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/transaction/SpringProvider.java @@ -0,0 +1,41 @@ +/* + * Copyright 2010 the original author or authors. + * + * 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. + */ + +package org.springframework.data.graph.neo4j.transaction; + +import org.neo4j.helpers.Service; +import org.neo4j.kernel.impl.core.KernelPanicEventGenerator; +import org.neo4j.kernel.impl.transaction.AbstractTransactionManager; +import org.neo4j.kernel.impl.transaction.TransactionManagerProvider; +import org.neo4j.kernel.impl.transaction.TxFinishHook; +import org.springframework.beans.factory.annotation.Configurable; + +@Configurable +@Service.Implementation( TransactionManagerProvider.class ) +public class SpringProvider extends TransactionManagerProvider +{ + public SpringProvider() + { + super( "spring-jta" ); + } + + @Override + protected AbstractTransactionManager loadTransactionManager( String txLogDir, + KernelPanicEventGenerator kpe, TxFinishHook rollbackHook ) + { + return new SpringServiceImpl(); + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/transaction/SpringServiceImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/transaction/SpringServiceImpl.java new file mode 100644 index 000000000..f416fd874 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/transaction/SpringServiceImpl.java @@ -0,0 +1,102 @@ +/* + * Copyright 2010 the original author or authors. + * + * 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. + */ + +package org.springframework.data.graph.neo4j.transaction; + +import org.neo4j.kernel.impl.transaction.AbstractTransactionManager; +import org.neo4j.kernel.impl.transaction.XaDataSourceManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.transaction.jta.JtaTransactionManager; + +import javax.transaction.*; + +/** + * @author Chris Gioran + */ +@Configurable +class SpringServiceImpl extends AbstractTransactionManager +{ + @Autowired + private JtaTransactionManager jtaTransactionManager; + + private TransactionManager delegate; + + SpringServiceImpl() + { + } + + @Override + public void init( XaDataSourceManager xaDsManager ) + { + delegate = jtaTransactionManager.getTransactionManager(); + } + + public void begin() throws NotSupportedException, SystemException + { + delegate.begin(); + } + + public void commit() throws RollbackException, HeuristicMixedException, + HeuristicRollbackException, SecurityException, + IllegalStateException, SystemException + { + delegate.commit(); + } + + public int getStatus() throws SystemException + { + return delegate.getStatus(); + } + + public Transaction getTransaction() throws SystemException + { + return delegate.getTransaction(); + } + + public void resume( Transaction tobj ) throws InvalidTransactionException, + IllegalStateException, SystemException + { + delegate.resume( tobj ); + } + + public void rollback() throws IllegalStateException, SecurityException, + SystemException + { + delegate.rollback(); + } + + public void setRollbackOnly() throws IllegalStateException, SystemException + { + delegate.setRollbackOnly(); + } + + public void setTransactionTimeout( int seconds ) throws SystemException + { + delegate.setTransactionTimeout( seconds ); + } + + public Transaction suspend() throws SystemException + { + return delegate.suspend(); + } + + @Override + public void stop() + { + // Currently a no-op + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/SynchronizationManager.java b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/transaction/SynchronizationManager.java similarity index 76% rename from spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/SynchronizationManager.java rename to spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/transaction/SynchronizationManager.java index 1bbc2f9d9..063cc3279 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/support/SynchronizationManager.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/graph/neo4j/transaction/SynchronizationManager.java @@ -1,4 +1,4 @@ -package org.springframework.data.graph.neo4j.support; +package org.springframework.data.graph.neo4j.transaction; /** * @author mh diff --git a/spring-data-neo4j/src/main/resources/META-INF/services/org.neo4j.kernel.impl.transaction.TransactionManagerProvider b/spring-data-neo4j/src/main/resources/META-INF/services/org.neo4j.kernel.impl.transaction.TransactionManagerProvider new file mode 100644 index 000000000..540a6813d --- /dev/null +++ b/spring-data-neo4j/src/main/resources/META-INF/services/org.neo4j.kernel.impl.transaction.TransactionManagerProvider @@ -0,0 +1 @@ +org.springframework.data.graph.neo4j.transaction.SpringProvider diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/support/ChainedTransactionManagerTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/transaction/ChainedTransactionManagerTest.java similarity index 93% rename from spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/support/ChainedTransactionManagerTest.java rename to spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/transaction/ChainedTransactionManagerTest.java index 416768cc3..497c4ac5c 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/support/ChainedTransactionManagerTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/transaction/ChainedTransactionManagerTest.java @@ -1,4 +1,4 @@ -package org.springframework.data.graph.neo4j.support; +package org.springframework.data.graph.neo4j.transaction; import org.hamcrest.Description; import org.hamcrest.Factory; @@ -9,10 +9,10 @@ import org.springframework.transaction.support.DefaultTransactionDefinition; import static junit.framework.Assert.*; import static org.junit.Assert.assertThat; -import static org.springframework.data.graph.neo4j.support.ChainedTransactionManagerTest.TestPlatformTransactionManager.createFailingTransactionManager; -import static org.springframework.data.graph.neo4j.support.ChainedTransactionManagerTest.TestPlatformTransactionManager.createNonFailingTransactionManager; -import static org.springframework.data.graph.neo4j.support.ChainedTransactionManagerTest.TransactionManagerMatcher.isCommitted; -import static org.springframework.data.graph.neo4j.support.ChainedTransactionManagerTest.TransactionManagerMatcher.wasRolledback; +import static org.springframework.data.graph.neo4j.transaction.ChainedTransactionManagerTest.TestPlatformTransactionManager.createFailingTransactionManager; +import static org.springframework.data.graph.neo4j.transaction.ChainedTransactionManagerTest.TestPlatformTransactionManager.createNonFailingTransactionManager; +import static org.springframework.data.graph.neo4j.transaction.ChainedTransactionManagerTest.TransactionManagerMatcher.isCommitted; +import static org.springframework.data.graph.neo4j.transaction.ChainedTransactionManagerTest.TransactionManagerMatcher.wasRolledback; import static org.springframework.transaction.HeuristicCompletionException.getStateString; /** diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/transaction/JOTMIntegrationTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/transaction/JOTMIntegrationTest.java new file mode 100644 index 000000000..2a7eac265 --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/graph/neo4j/transaction/JOTMIntegrationTest.java @@ -0,0 +1,109 @@ +package org.springframework.data.graph.neo4j.transaction; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.NotFoundException; +import org.neo4j.graphdb.index.IndexHits; +import org.neo4j.kernel.Config; +import org.neo4j.kernel.EmbeddedGraphDatabase; +import org.objectweb.jotm.Current; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.transaction.jta.JtaTransactionManager; +import org.springframework.transaction.jta.ManagedTransactionAdapter; + +import javax.transaction.NotSupportedException; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * @author mh + * @since 21.02.11 + */ + +public class JOTMIntegrationTest { + private ClassPathXmlApplicationContext ctx; + private GraphDatabaseService gds; + + @Before + public void setUp() throws Exception { + ctx = new ClassPathXmlApplicationContext("classpath:spring-tx-text-context.xml"); + gds = ctx.getBean(GraphDatabaseService.class); + } + + @After + public void tearDown() throws Exception { + if (ctx != null) ctx.close(); + } + + @Test + public void createdNodeShouldBeFoundAfterCommit() throws Exception { + org.neo4j.graphdb.Transaction transaction = gds.beginTx(); + Node node = null; + try { + node = gds.createNode(); + assertNotNull(node); + transaction.success(); + } finally { + transaction.finish(); + } + Node readBackOutsideOfTx = gds.getNodeById(node.getId()); + assertEquals(node, readBackOutsideOfTx); + try { + transaction = gds.beginTx(); + Node readBackInsideOfTx = gds.getNodeById(node.getId()); + assertEquals(node, readBackInsideOfTx); + transaction.success(); + } finally { + transaction.finish(); + } + } + + @Test + public void indexedNodeShouldBeFound() throws Exception { + org.neo4j.graphdb.Transaction transaction = gds.beginTx(); + Node node = null; + try { + node = gds.createNode(); + gds.index().forNodes("node").add(node, "name", "value"); + transaction.success(); + } finally { + transaction.finish(); + } + Node retrievedNode = gds.index().forNodes("node").get("name", "value").getSingle(); + assertEquals(node,retrievedNode); + } + + @Test(expected = NotFoundException.class) + public void createdNodeShouldBeNotAvailableAfterRollback() throws Exception { + org.neo4j.graphdb.Transaction tx = gds.beginTx(); + long nodeId=0; + try { + Node node = gds.createNode(); + nodeId = node.getId(); + tx.failure(); + } finally { + tx.finish(); + } + gds.getNodeById(nodeId); + } + + @Test + public void databaseConfiguredWithSpringJtaShouldUseJtaTransactionManager() throws SystemException, NotSupportedException { + Map