Index: docs/reference/src/main/docbook/en-US/content/connectors/jdbc_storage.xml =================================================================== --- docs/reference/src/main/docbook/en-US/content/connectors/jdbc_storage.xml (revision 2507) +++ docs/reference/src/main/docbook/en-US/content/connectors/jdbc_storage.xml (working copy) @@ -129,6 +129,16 @@ + isolationLevel + Optional property that, if used, de + of the java.sql.Connection#TRANSACTION_* c + "TRANSACTION_READ_COMMITTED", "TRANSACTION + not all JDBC drivers support all isolation + When this property is not used, th + isolation level is currently set o + + + largeValueSizeInBytes An advanced boolean property that controls the size of property values at which they are considered to be "large values". Index: extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/JpaConnectorI18n.java =================================================================== --- extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/JpaConnectorI18n.java (revision 2507) +++ extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/JpaConnectorI18n.java (working copy) @@ -47,6 +47,7 @@ public final class JpaConnectorI18n { public static I18n locationShouldHavePathAndOrProperty; public static I18n invalidUuidForWorkspace; public static I18n invalidReferences; + public static I18n invalidIsolationLevel; public static I18n unableToDeleteBecauseOfReferences; public static I18n workspaceAlreadyExists; @@ -95,6 +96,9 @@ public final class JpaConnectorI18n { public static I18n urlPropertyDescription; public static I18n urlPropertyLabel; public static I18n urlPropertyCategory; + public static I18n isolationLevelPropertyDescription; + public static I18n isolationLevelPropertyLabel; + public static I18n isolationLevelPropertyCategory; public static I18n driverClassNamePropertyDescription; public static I18n driverClassNamePropertyLabel; public static I18n driverClassNamePropertyCategory; Index: extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/JpaSource.java =================================================================== --- extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/JpaSource.java (revision 2507) +++ extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/JpaSource.java (working copy) @@ -23,6 +23,7 @@ */ package org.modeshape.connector.store.jpa; +import java.sql.Connection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -115,6 +116,7 @@ public class JpaSource implements RepositorySource, ObjectFactory { protected static final String URL = "url"; protected static final String DRIVER_CLASS_NAME = "driverClassName"; protected static final String DRIVER_CLASSLOADER_NAME = "driverClassloaderName"; + protected static final String ISOLATION_LEVEL = "isolationLevel"; protected static final String MAXIMUM_CONNECTIONS_IN_POOL = "maximumConnectionsInPool"; protected static final String MINIMUM_CONNECTIONS_IN_POOL = "minimumConnectionsInPool"; protected static final String MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS = "maximumConnectionIdleTimeInSeconds"; @@ -168,6 +170,8 @@ public class JpaSource implements RepositorySource, ObjectFactory { */ public static final String DEFAULT_NAME_OF_DEFAULT_WORKSPACE = "default"; + private static final int DEFAULT_ISOLATION_LEVEL = Connection.TRANSACTION_REPEATABLE_READ; + private static final int DEFAULT_RETRY_LIMIT = 0; private static final int DEFAULT_CACHE_TIME_TO_LIVE_IN_SECONDS = 60 * 5; // 5 minutes private static final int DEFAULT_MAXIMUM_FETCH_DEPTH = 3; @@ -308,6 +312,11 @@ public class JpaSource implements RepositorySource, ObjectFactory { @Category( i18n = JpaConnectorI18n.class, value = "defaultWorkspaceNamePropertyCategory" ) private volatile String defaultWorkspace = DEFAULT_NAME_OF_DEFAULT_WORKSPACE; + @Description( i18n = JpaConnectorI18n.class, value = "isolationLevelPropertyDescription" ) + @Label( i18n = JpaConnectorI18n.class, value = "isolationLevelPropertyLabel" ) + @Category( i18n = JpaConnectorI18n.class, value = "isolationLevelPropertyCategory" ) + private volatile int isolationLevel = DEFAULT_ISOLATION_LEVEL; + @Description( i18n = JpaConnectorI18n.class, value = "predefinedWorkspacesPropertyDescription" ) @Label( i18n = JpaConnectorI18n.class, value = "predefinedWorkspacesPropertyLabel" ) @Category( i18n = JpaConnectorI18n.class, value = "predefinedWorkspacesPropertyCategory" ) @@ -903,6 +912,34 @@ public class JpaSource implements RepositorySource, ObjectFactory { } /** + * @return isolationLevel + */ + public int getIsolationLevel() { + return isolationLevel; + } + + /** + * @param isolationLevel Sets isolationLevel to the specified value. + */ + public synchronized void setIsolationLevel( Integer isolationLevel ) { + if (isolationLevel == null) isolationLevel = DEFAULT_ISOLATION_LEVEL; + + if (isolationLevel != Connection.TRANSACTION_NONE && isolationLevel != Connection.TRANSACTION_READ_COMMITTED + && isolationLevel != Connection.TRANSACTION_READ_UNCOMMITTED + && isolationLevel != Connection.TRANSACTION_REPEATABLE_READ && isolationLevel != Connection.TRANSACTION_SERIALIZABLE) { + throw new RepositorySourceException(this.name, JpaConnectorI18n.invalidIsolationLevel.text(isolationLevel)); + } + + if (this.isolationLevel == isolationLevel) return; + + EntityManagers oldEntityManagers = this.entityManagers; + this.entityManagers = null; + oldEntityManagers.close(); + + this.isolationLevel = isolationLevel; + } + + /** * {@inheritDoc} * * @see org.modeshape.graph.connector.RepositorySource#initialize(org.modeshape.graph.connector.RepositoryContext) @@ -930,6 +967,7 @@ public class JpaSource implements RepositorySource, ObjectFactory { ref.add(new StringRefAddr(URL, getUrl())); ref.add(new StringRefAddr(DRIVER_CLASS_NAME, getDriverClassName())); ref.add(new StringRefAddr(DRIVER_CLASSLOADER_NAME, getDriverClassloaderName())); + ref.add(new StringRefAddr(ISOLATION_LEVEL, Integer.toString(getIsolationLevel()))); ref.add(new StringRefAddr(MAXIMUM_CONNECTIONS_IN_POOL, Integer.toString(getMaximumConnectionsInPool()))); ref.add(new StringRefAddr(MINIMUM_CONNECTIONS_IN_POOL, Integer.toString(getMinimumConnectionsInPool()))); ref.add(new StringRefAddr(MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS, @@ -994,6 +1032,7 @@ public class JpaSource implements RepositorySource, ObjectFactory { String url = values.get(URL); String driverClassName = values.get(DRIVER_CLASS_NAME); String driverClassloaderName = values.get(DRIVER_CLASSLOADER_NAME); + String isolationLevel = values.get(ISOLATION_LEVEL); String maxConnectionsInPool = values.get(MAXIMUM_CONNECTIONS_IN_POOL); String minConnectionsInPool = values.get(MINIMUM_CONNECTIONS_IN_POOL); String maxConnectionIdleTimeInSec = values.get(MAXIMUM_CONNECTION_IDLE_TIME_IN_SECONDS); @@ -1028,6 +1067,7 @@ public class JpaSource implements RepositorySource, ObjectFactory { if (url != null) source.setUrl(url); if (driverClassName != null) source.setDriverClassName(driverClassName); if (driverClassloaderName != null) source.setDriverClassloaderName(driverClassloaderName); + if (isolationLevel != null) source.setIsolationLevel(Integer.parseInt(isolationLevel)); if (maxConnectionsInPool != null) source.setMaximumConnectionsInPool(Integer.parseInt(maxConnectionsInPool)); if (minConnectionsInPool != null) source.setMinimumConnectionsInPool(Integer.parseInt(minConnectionsInPool)); if (maxConnectionIdleTimeInSec != null) source.setMaximumConnectionIdleTimeInSeconds(Integer.parseInt(maxConnectionIdleTimeInSec)); @@ -1069,6 +1109,7 @@ public class JpaSource implements RepositorySource, ObjectFactory { // Set the Hibernate properties used in all situations ... setProperty(configurator, "hibernate.dialect", this.dialect); + setProperty(configurator, "hibernate.connection.isolation", this.isolationLevel); // Configure additional properties, which may be overridden by subclasses ... configure(configurator); Index: extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/model/simple/SimpleJpaConnection.java =================================================================== --- extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/model/simple/SimpleJpaConnection.java (revision 2507) +++ extensions/modeshape-connector-store-jpa/src/main/java/org/modeshape/connector/store/jpa/model/simple/SimpleJpaConnection.java (working copy) @@ -163,4 +163,20 @@ public class SimpleJpaConnection implements RepositoryConnection { logger.trace(this.getClass().getSimpleName() + ".execute(...) took " + sw.getTotalDuration()); } } + + /* + + This method is needed only to support the SimpleJpaSourceTest#shouldAllowChangingIsolationLevel() test. + + EntityManager entityManager() { + EntityManager entityManager = this.entityManager; + + if (entityManager == null) { + acquireRepository(); + entityManager = this.entityManager; + } + + return entityManager; + } + */ } Index: extensions/modeshape-connector-store-jpa/src/main/resources/org/modeshape/connector/store/jpa/JpaConnectorI18n.properties =================================================================== --- extensions/modeshape-connector-store-jpa/src/main/resources/org/modeshape/connector/store/jpa/JpaConnectorI18n.properties (revision 2507) +++ extensions/modeshape-connector-store-jpa/src/main/resources/org/modeshape/connector/store/jpa/JpaConnectorI18n.properties (working copy) @@ -36,6 +36,9 @@ locationShouldHavePathAndOrProperty = The source {0} is unable to find a node wi invalidUuidForWorkspace = There is no node with UUID "{0}" in workspace "{1}" invalidReferences = One or more references were invalid in {0} unableToDeleteBecauseOfReferences = At least one deleted node is referenced by a node that is not being deleted +invalidIsolationLevel = An invalid isolation level "{0}" was specified and will be ignored. Valid isolation levels are TRANSACTION_NONE, TRANSACTION_READ_COMMITTED, TRANSACTION_READ_UNCOMMITTED, TRANSACTION_REPEATABLE_READ, and TRANSACTION_SERIALIZABLE from the java.sql.Connection class. + + workspaceAlreadyExists = The source {0} already has a workspace named "{1}" workspaceDoesNotExist = The source {0} has no workspace named "{1}" @@ -95,6 +98,10 @@ driverClassloaderNamePropertyDescription = The name of the class loader or class driverClassloaderNamePropertyLabel = Driver Classloader Name driverClassloaderNamePropertyCategory = Driver +isolationLevelPropertyDescription = The isolation level that should be used when connecting to the database. +isolationLevelPropertyLabel = Database Isolation Level +isolationLevelPropertyCategory = Driver + maximumConnectionsInPoolPropertyDescription = The maximum number of connections that may be in the connection pool. The default is "5". This is not required if the DataSource is found in JNDI. maximumConnectionsInPoolPropertyLabel = Maximum Connection Pool Size maximumConnectionsInPoolPropertyCategory = Driver Index: extensions/modeshape-connector-store-jpa/src/test/java/org/modeshape/connector/store/jpa/model/simple/SimpleJpaSourceTest.java =================================================================== --- extensions/modeshape-connector-store-jpa/src/test/java/org/modeshape/connector/store/jpa/model/simple/SimpleJpaSourceTest.java (revision 2507) +++ extensions/modeshape-connector-store-jpa/src/test/java/org/modeshape/connector/store/jpa/model/simple/SimpleJpaSourceTest.java (working copy) @@ -24,6 +24,8 @@ package org.modeshape.connector.store.jpa.model.simple; import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; import org.modeshape.connector.store.jpa.JpaSource; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.Subgraph; @@ -31,8 +33,6 @@ import org.modeshape.graph.connector.RepositoryConnection; import org.modeshape.graph.connector.RepositoryConnectionFactory; import org.modeshape.graph.connector.RepositoryContext; import org.modeshape.graph.observe.Observer; -import org.junit.Before; -import org.junit.Test; public class SimpleJpaSourceTest { @@ -50,7 +50,7 @@ public class SimpleJpaSourceTest { source.setUsername("sa"); source.setPassword(""); source.setUrl("jdbc:hsqldb:mem:."); - source.setShowSql(true); + source.setShowSql(false); source.setAutoGenerateSchema("create"); source.initialize(new RepositoryContext() { @@ -82,4 +82,42 @@ public class SimpleJpaSourceTest { connection.ping(1, TimeUnit.SECONDS); connection.close(); } + + /* + The test below helps establish that the hibernate.connection.isolation property is being passed to Hibernate + correctly and respected by Hibernate, but the test case is very brittle and should not be checked in. + + If you wish to reverify this behavior, please uncomment the entityManager() method in SimpleJpaConnection. + @FixFor("MODE-983") + @Test + public void shouldAllowChangingIsolationLevel() throws Exception { + RepositoryConnection conn; + SimpleJpaConnection jpaConn; + EntityManagerImpl emgr; + int txLevel; + + // txLevel = Connection.TRANSACTION_SERIALIZABLE; + + txLevel = Connection.TRANSACTION_REPEATABLE_READ; + source.setIsolationLevel(txLevel); + conn = source.getConnection(); + assertThat(conn, instanceOf(SimpleJpaConnection.class)); + + jpaConn = (SimpleJpaConnection)conn; + emgr = (EntityManagerImpl)jpaConn.entityManager(); + assertTrue(emgr.getSession().connection().getTransactionIsolation() == txLevel); + jpaConn.close(); + + txLevel = Connection.TRANSACTION_SERIALIZABLE; + source.setIsolationLevel(txLevel); + conn = source.getConnection(); + assertThat(conn, instanceOf(SimpleJpaConnection.class)); + + jpaConn = (SimpleJpaConnection)conn; + emgr = (EntityManagerImpl)jpaConn.entityManager(); + assertTrue(emgr.getSession().connection().getTransactionIsolation() == txLevel); + jpaConn.close(); + + } + */ }