diff --git a/pom.xml b/pom.xml index 8c3bd09..c67f89b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ - @@ -8,7 +8,8 @@ org.openrdf.sesame sesame-spring - 2.7.13 + + 2.8.0-beta1 ${project.artifactId} @@ -28,6 +29,11 @@ scm:git:https://github.com/ameingast/sesame-spring.git + + Github + https://github.com/ameingast/sesame-spring/issues + + Apache License 2.0 @@ -82,14 +88,14 @@ org.openrdf.sesame - sesame-queryparser-sparql + sesame-repository-sail ${sesame.version} - test org.openrdf.sesame - sesame-repository-sail + sesame-queryparser-sparql ${sesame.version} + test org.openrdf.sesame diff --git a/src/main/java/org/openrdf/spring/DynamicRepositoryManagerConnectionFactory.java b/src/main/java/org/openrdf/spring/DynamicRepositoryManagerConnectionFactory.java index 580b7c6..e9cccb2 100644 --- a/src/main/java/org/openrdf/spring/DynamicRepositoryManagerConnectionFactory.java +++ b/src/main/java/org/openrdf/spring/DynamicRepositoryManagerConnectionFactory.java @@ -9,8 +9,8 @@ import org.openrdf.repository.manager.RepositoryManager; import org.springframework.beans.factory.DisposableBean; +import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; /** *

{@link RepositoryManagerConnectionFactory} handles connections to a multiple corresponding @@ -53,7 +53,7 @@ public DynamicRepositoryManagerConnectionFactory(RepositoryManager repositoryMan this.repositoryManager = repositoryManager; this.repositoryImplConfig = repositoryImplConfig; this.repositoryIdProvider = repositoryIdProvider; - this.repositoryConnectionFactoryMap = new ConcurrentHashMap(128); + this.repositoryConnectionFactoryMap = new HashMap(128); } /** @@ -96,7 +96,7 @@ public SesameTransactionObject getLocalTransactionObject() { return getRepositoryConnectionFactory().getLocalTransactionObject(); } - private RepositoryConnectionFactory getRepositoryConnectionFactory() { + private synchronized RepositoryConnectionFactory getRepositoryConnectionFactory() { String repositoryId = repositoryIdProvider.getRepositoryId(); RepositoryConnectionFactory repositoryConnectionFactory = repositoryConnectionFactoryMap.get(repositoryId); @@ -109,12 +109,6 @@ private RepositoryConnectionFactory getRepositoryConnectionFactory() { } private RepositoryConnectionFactory initializeRepositoryConnectionFactory(String repositoryId) { - RepositoryConnectionFactory repositoryConnectionFactory = repositoryConnectionFactoryMap.get(repositoryId); - - if (repositoryConnectionFactory != null) { - return repositoryConnectionFactory; - } - try { Repository repository = repositoryManager.getRepository(repositoryId); diff --git a/src/main/java/org/openrdf/spring/IsolationLevelAdapter.java b/src/main/java/org/openrdf/spring/IsolationLevelAdapter.java new file mode 100644 index 0000000..ec5d310 --- /dev/null +++ b/src/main/java/org/openrdf/spring/IsolationLevelAdapter.java @@ -0,0 +1,44 @@ +package org.openrdf.spring; + +import org.openrdf.IsolationLevel; +import org.openrdf.IsolationLevels; +import org.openrdf.sail.Sail; +import org.springframework.transaction.InvalidIsolationLevelException; +import org.springframework.transaction.TransactionDefinition; + +/** + *

Adapter to convert spring {@link TransactionDefinition} isolation levels to corresponding OpenRDF + * {@link org.openrdf.IsolationLevel}s.

+ *

+ *

The conversion depends on the provided {@link org.openrdf.sail.Sail} and its transaction capabilities. + * If the {@link org.openrdf.sail.Sail} is not compatible with the provided isolation level, an + * {@link org.springframework.transaction.InvalidIsolationLevelException} is thrown.

+ * + * @author ameingast@gmail.com + */ +class IsolationLevelAdapter { + static IsolationLevel adaptToRdfIsolation(Sail sail, int springIsolation) { + switch (springIsolation) { + case TransactionDefinition.ISOLATION_DEFAULT: + return sail.getDefaultIsolationLevel(); + case TransactionDefinition.ISOLATION_READ_COMMITTED: + return determineIsolationLevel(sail, IsolationLevels.READ_COMMITTED); + case TransactionDefinition.ISOLATION_READ_UNCOMMITTED: + return determineIsolationLevel(sail, IsolationLevels.READ_UNCOMMITTED); + case TransactionDefinition.ISOLATION_REPEATABLE_READ: + throw new InvalidIsolationLevelException("Unsupported isolation level for sail: " + sail + ": " + springIsolation); + case TransactionDefinition.ISOLATION_SERIALIZABLE: + return determineIsolationLevel(sail, IsolationLevels.SERIALIZABLE); + default: + throw new InvalidIsolationLevelException("Unsupported isolation level for sail: " + sail + ": " + springIsolation); + } + } + + private static IsolationLevel determineIsolationLevel(Sail sail, IsolationLevel isolationLevel) { + if (sail.getSupportedIsolationLevels().contains(isolationLevel)) { + return isolationLevel; + } else { + throw new InvalidIsolationLevelException("Unsupported isolation level for sail: " + sail + ": " + isolationLevel); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/openrdf/spring/RepositoryConnectionFactory.java b/src/main/java/org/openrdf/spring/RepositoryConnectionFactory.java index 23d3ca8..0665714 100644 --- a/src/main/java/org/openrdf/spring/RepositoryConnectionFactory.java +++ b/src/main/java/org/openrdf/spring/RepositoryConnectionFactory.java @@ -50,7 +50,7 @@ public RepositoryConnection getConnection() { } if (!repositoryConnection.isActive()) { - throw new SesameTransactionException("No transaction active"); + repositoryConnection.begin(); } } catch (RepositoryException e) { throw new SesameTransactionException(e); @@ -102,7 +102,6 @@ public void closeConnection() { @Override public SesameTransactionObject createTransaction() throws RepositoryException { RepositoryConnection repositoryConnection = repository.getConnection(); - repositoryConnection.begin(); SesameTransactionObject sesameTransactionObject = new SesameTransactionObject(repositoryConnection); localTransactionObject.set(sesameTransactionObject); @@ -127,14 +126,12 @@ public void endTransaction(boolean rollback) throws RepositoryException { throw new SesameTransactionException("Connection closed during transaction"); } - if (!repositoryConnection.isActive()) { - throw new SesameTransactionException("No transaction active"); - } - - if (rollback) { - repositoryConnection.rollback(); - } else { - repositoryConnection.commit(); + if (repositoryConnection.isActive()) { + if (rollback) { + repositoryConnection.rollback(); + } else { + repositoryConnection.commit(); + } } } diff --git a/src/main/java/org/openrdf/spring/SesameTransactionManager.java b/src/main/java/org/openrdf/spring/SesameTransactionManager.java index 61ffa02..9a2fbeb 100644 --- a/src/main/java/org/openrdf/spring/SesameTransactionManager.java +++ b/src/main/java/org/openrdf/spring/SesameTransactionManager.java @@ -1,12 +1,19 @@ package org.openrdf.spring; +import org.openrdf.IsolationLevel; +import org.openrdf.repository.Repository; +import org.openrdf.repository.RepositoryConnection; import org.openrdf.repository.RepositoryException; +import org.openrdf.repository.sail.SailRepository; +import org.openrdf.sail.Sail; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionSystemException; import org.springframework.transaction.support.AbstractPlatformTransactionManager; import org.springframework.transaction.support.DefaultTransactionStatus; +import static org.openrdf.spring.IsolationLevelAdapter.adaptToRdfIsolation; + /** *

{@link SesameTransactionManager} manages the transaction lifecycle of a {@link SesameTransactionObject}.

*

@@ -65,14 +72,28 @@ protected boolean isExistingTransaction(Object transaction) throws TransactionEx * {@see AbstractPlatformTransactionManager#doBegin} */ @Override - protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { + protected void doBegin(Object transaction, TransactionDefinition transactionDefinition) throws TransactionException { SesameTransactionObject sesameTransactionObject = (SesameTransactionObject) transaction; - sesameTransactionObject.setTimeout(definition.getTimeout()); - sesameTransactionObject.setIsolationLevel(definition.getIsolationLevel()); - sesameTransactionObject.setPropagationBehavior(definition.getPropagationBehavior()); - sesameTransactionObject.setReadOnly(definition.isReadOnly()); - sesameTransactionObject.setName("[" + Thread.currentThread().getName() + "] " + definition.getName()); + sesameTransactionObject.setTimeout(transactionDefinition.getTimeout()); + sesameTransactionObject.setIsolationLevel(transactionDefinition.getIsolationLevel()); + sesameTransactionObject.setPropagationBehavior(transactionDefinition.getPropagationBehavior()); + sesameTransactionObject.setReadOnly(transactionDefinition.isReadOnly()); + sesameTransactionObject.setName(Thread.currentThread().getName() + " " + transactionDefinition.getName()); + + setIsolationLevel(sesameTransactionObject, transactionDefinition); + } + + private void setIsolationLevel(SesameTransactionObject sesameTransactionObject, TransactionDefinition transactionDefinition) { + RepositoryConnection repositoryConnection = sesameTransactionObject.getRepositoryConnection(); + Repository repository = repositoryConnection.getRepository(); + + if (repository instanceof SailRepository) { + Sail sail = ((SailRepository) repository).getSail(); + IsolationLevel isolationLevel = adaptToRdfIsolation(sail, transactionDefinition.getIsolationLevel()); + + repositoryConnection.setIsolationLevel(isolationLevel); + } } /** diff --git a/src/test/java/org/openrdf/spring/BaseTest.java b/src/test/java/org/openrdf/spring/BaseTest.java new file mode 100644 index 0000000..a84f7a7 --- /dev/null +++ b/src/test/java/org/openrdf/spring/BaseTest.java @@ -0,0 +1,69 @@ +package org.openrdf.spring; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.runner.RunWith; +import org.openrdf.model.URI; +import org.openrdf.model.ValueFactory; +import org.openrdf.model.impl.ValueFactoryImpl; +import org.openrdf.query.BindingSet; +import org.openrdf.query.QueryLanguage; +import org.openrdf.query.TupleQuery; +import org.openrdf.query.TupleQueryResult; +import org.openrdf.repository.RepositoryConnection; +import org.openrdf.repository.RepositoryException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@Ignore +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = "/repositoryTestContext.xml") +public abstract class BaseTest { + @Autowired + protected SesameConnectionFactory repositoryConnectionFactory; + + @Autowired + protected SesameConnectionFactory repositoryManagerConnectionFactory; + + protected static void assertDataPresent(SesameConnectionFactory sesameConnectionFactory) throws Exception { + RepositoryConnection connection = sesameConnectionFactory.getConnection(); + final TupleQuery tupleQuery = connection.prepareTupleQuery(QueryLanguage.SPARQL, "SELECT ?s ?o WHERE { ?s ?o . }"); + TupleQueryResult result = tupleQuery.evaluate(); + + withTupleQueryResult(result, new TupleQueryResultHandler() { + @Override + public void handle(TupleQueryResult tupleQueryResult) throws Exception { + Assert.assertTrue(tupleQueryResult.hasNext()); + + BindingSet bindingSet = tupleQueryResult.next(); + + Assert.assertEquals("http://example.com/a", bindingSet.getBinding("s").getValue().stringValue()); + Assert.assertEquals("http://example.com/c", bindingSet.getBinding("o").getValue().stringValue()); + } + }); + } + + private static void withTupleQueryResult(TupleQueryResult tupleQueryResult, + TupleQueryResultHandler tupleQueryResultHandler) throws Exception { + try { + tupleQueryResultHandler.handle(tupleQueryResult); + } finally { + tupleQueryResult.close(); + } + } + + protected static void addData(SesameConnectionFactory sesameConnectionFactory) throws RepositoryException { + ValueFactory f = ValueFactoryImpl.getInstance(); + URI a = f.createURI("http://example.com/a"); + URI b = f.createURI("http://example.com/b"); + URI c = f.createURI("http://example.com/c"); + + RepositoryConnection connection = sesameConnectionFactory.getConnection(); + connection.add(a, b, c); + } + + static interface TupleQueryResultHandler { + void handle(TupleQueryResult tupleQueryResult) throws Exception; + } +} diff --git a/src/test/java/org/openrdf/spring/RepositoryConnectionFactoryTest.java b/src/test/java/org/openrdf/spring/RepositoryConnectionFactoryTest.java index 41a2fc3..090322b 100644 --- a/src/test/java/org/openrdf/spring/RepositoryConnectionFactoryTest.java +++ b/src/test/java/org/openrdf/spring/RepositoryConnectionFactoryTest.java @@ -2,38 +2,33 @@ import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.openrdf.model.URI; -import org.openrdf.model.ValueFactory; -import org.openrdf.model.impl.ValueFactoryImpl; -import org.openrdf.query.BindingSet; -import org.openrdf.query.QueryLanguage; -import org.openrdf.query.TupleQuery; -import org.openrdf.query.TupleQueryResult; +import org.openrdf.IsolationLevel; +import org.openrdf.IsolationLevels; import org.openrdf.repository.RepositoryConnection; import org.openrdf.repository.RepositoryException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.openrdf.repository.sail.SailRepository; +import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(locations = "/repositoryTestContext.xml") -public class RepositoryConnectionFactoryTest { - @Autowired - protected SesameConnectionFactory repositoryConnectionFactory; - +public class RepositoryConnectionFactoryTest extends BaseTest { @Test(expected = SesameTransactionException.class) public void testFactoryDoesNotCreateConnection() throws RepositoryException { repositoryConnectionFactory.getConnection(); } + @Test + @Transactional("transactionManager") + public void testEmptyTransaction() { + Assert.assertEquals(0, 0); + } + @Test @Transactional("transactionManager") public void testTransactionCreatesConnection() throws RepositoryException { RepositoryConnection currentConnection = repositoryConnectionFactory.getConnection(); + Assert.assertNotNull(currentConnection); } @@ -56,47 +51,44 @@ public void testWrapTransactions() { } @Transactional("transactionManager") - protected RepositoryConnection transactionScope() { + public RepositoryConnection transactionScope() { return repositoryConnectionFactory.getConnection(); } - protected RepositoryConnection transactionScopeWithoutAnnotation() { + public RepositoryConnection transactionScopeWithoutAnnotation() { return repositoryConnectionFactory.getConnection(); } @Test @Transactional("transactionManager") public void testWriteData() throws Exception { - addData(); - assertDataPresent(); + addData(repositoryConnectionFactory); + assertDataPresent(repositoryConnectionFactory); } - protected void assertDataPresent() throws Exception { + @Test + @Transactional(value = "transactionManager") + public void testTransactionIsolationLevel() { RepositoryConnection connection = repositoryConnectionFactory.getConnection(); - final TupleQuery tupleQuery = connection.prepareTupleQuery(QueryLanguage.SPARQL, "SELECT ?s ?p ?o WHERE { ?s ?p ?o . }"); - TupleQueryResult result = tupleQuery.evaluate(); + SailRepository sailRepository = (SailRepository) connection.getRepository(); + IsolationLevel defaultIsolationLevel = sailRepository.getSail().getDefaultIsolationLevel(); - SesameResultHandlers.withTupleQueryResult(result, new SesameResultHandlers.TupleQueryResultHandler() { - @Override - public void handle(TupleQueryResult tupleQueryResult) throws Exception { - Assert.assertTrue(tupleQueryResult.hasNext()); + Assert.assertEquals(defaultIsolationLevel, connection.getIsolationLevel()); + } - BindingSet bindingSet = tupleQueryResult.next(); + @Test + @Transactional(value = "transactionManager", isolation = Isolation.SERIALIZABLE) + public void testTransactionWithSerializableIsolationLevel() { + RepositoryConnection connection = repositoryConnectionFactory.getConnection(); - Assert.assertEquals("http://example.com/a", bindingSet.getBinding("s").getValue().stringValue()); - Assert.assertEquals("http://example.com/b", bindingSet.getBinding("p").getValue().stringValue()); - Assert.assertEquals("http://example.com/c", bindingSet.getBinding("o").getValue().stringValue()); - } - }); + Assert.assertEquals(IsolationLevels.SERIALIZABLE, connection.getIsolationLevel()); } - protected void addData() throws RepositoryException { - ValueFactory f = ValueFactoryImpl.getInstance(); - URI a = f.createURI("http://example.com/a"); - URI b = f.createURI("http://example.com/b"); - URI c = f.createURI("http://example.com/c"); - + @Test + @Transactional(value = "transactionManager", isolation = Isolation.READ_UNCOMMITTED) + public void testTransactionWithReadUnCommittedIsolationLevel() { RepositoryConnection connection = repositoryConnectionFactory.getConnection(); - connection.add(a, b, c); + + Assert.assertEquals(IsolationLevels.READ_UNCOMMITTED, connection.getIsolationLevel()); } } diff --git a/src/test/java/org/openrdf/spring/RepositoryManagerConnectionFactoryTest.java b/src/test/java/org/openrdf/spring/RepositoryManagerConnectionFactoryTest.java index fa4b46a..9a3389f 100644 --- a/src/test/java/org/openrdf/spring/RepositoryManagerConnectionFactoryTest.java +++ b/src/test/java/org/openrdf/spring/RepositoryManagerConnectionFactoryTest.java @@ -2,29 +2,13 @@ import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.openrdf.model.URI; -import org.openrdf.model.ValueFactory; -import org.openrdf.model.impl.ValueFactoryImpl; -import org.openrdf.query.BindingSet; -import org.openrdf.query.QueryLanguage; -import org.openrdf.query.TupleQuery; -import org.openrdf.query.TupleQueryResult; import org.openrdf.repository.RepositoryConnection; import org.openrdf.repository.RepositoryException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(locations = "/repositoryTestContext.xml") -public class RepositoryManagerConnectionFactoryTest { - @Autowired - private SesameConnectionFactory repositoryManagerConnectionFactory; - +public class RepositoryManagerConnectionFactoryTest extends BaseTest { @Test(expected = SesameTransactionException.class) public void testFactoryDoesNotCreateConnection() throws RepositoryException { repositoryManagerConnectionFactory.getConnection(); @@ -34,6 +18,7 @@ public void testFactoryDoesNotCreateConnection() throws RepositoryException { @Transactional("repositoryTransactionManager") public void testTransactionCreatesConnection() throws RepositoryException { RepositoryConnection currentConnection = repositoryManagerConnectionFactory.getConnection(); + Assert.assertNotNull(currentConnection); } @@ -67,35 +52,7 @@ protected RepositoryConnection transactionScopeWithoutAnnotation() { @Test @Transactional("repositoryTransactionManager") public void testWriteData() throws Exception { - addData(); - assertDataPresent(); - } - - protected void assertDataPresent() throws Exception { - RepositoryConnection connection = repositoryManagerConnectionFactory.getConnection(); - final TupleQuery tupleQuery = connection.prepareTupleQuery(QueryLanguage.SPARQL, "SELECT ?s ?o WHERE { ?s ?o . }"); - TupleQueryResult result = tupleQuery.evaluate(); - - SesameResultHandlers.withTupleQueryResult(result, new SesameResultHandlers.TupleQueryResultHandler() { - @Override - public void handle(TupleQueryResult tupleQueryResult) throws Exception { - Assert.assertTrue(tupleQueryResult.hasNext()); - - BindingSet bindingSet = tupleQueryResult.next(); - - Assert.assertEquals("http://example.com/a", bindingSet.getBinding("s").getValue().stringValue()); - Assert.assertEquals("http://example.com/c", bindingSet.getBinding("o").getValue().stringValue()); - } - }); - } - - protected void addData() throws RepositoryException { - ValueFactory f = ValueFactoryImpl.getInstance(); - URI a = f.createURI("http://example.com/a"); - URI b = f.createURI("http://example.com/b"); - URI c = f.createURI("http://example.com/c"); - - RepositoryConnection connection = repositoryManagerConnectionFactory.getConnection(); - connection.add(a, b, c); + addData(repositoryManagerConnectionFactory); + assertDataPresent(repositoryManagerConnectionFactory); } } diff --git a/src/test/java/org/openrdf/spring/SesameResultHandlers.java b/src/test/java/org/openrdf/spring/SesameResultHandlers.java deleted file mode 100644 index 36b102f..0000000 --- a/src/test/java/org/openrdf/spring/SesameResultHandlers.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.openrdf.spring; - -import org.openrdf.query.TupleQueryResult; - -public class SesameResultHandlers { - public static void withTupleQueryResult(TupleQueryResult tupleQueryResult, - TupleQueryResultHandler tupleQueryResultHandler) throws Exception { - try { - tupleQueryResultHandler.handle(tupleQueryResult); - } finally { - tupleQueryResult.close(); - } - } - - public static interface TupleQueryResultHandler { - void handle(TupleQueryResult tupleQueryResult) throws Exception; - } -}