From ccca605865ea8e0f81268f0d1ba05284a349e5bc Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Fri, 27 Mar 2020 11:29:40 +0000 Subject: [PATCH] ENT-5134 Discoverable Hibernate Session Factory Factory (#6091) * Introduce CordaSessionFactoryFactory interface and the H2 implememntation * Load SessionFactoryFactory via service loader * Add Postgres SessionFactoryFactory * Add extraConfiguration function for SessionFactoryFactory implementations to expose special config values. --- .../persistence/HibernateConfiguration.kt | 153 +++--------------- .../factory/BaseSessionFactoryFactory.kt | 138 ++++++++++++++++ .../factory/CordaSessionFactoryFactory.kt | 20 +++ .../factory/H2SessionFactoryFactory.kt | 27 ++++ .../factory/PostgresSessionFactoryFactory.kt | 41 +++++ ...istence.factory.CordaSessionFactoryFactory | 2 + ...ibernateConfigurationFactoryLoadingTest.kt | 26 +++ 7 files changed, 278 insertions(+), 129 deletions(-) create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/BaseSessionFactoryFactory.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/CordaSessionFactoryFactory.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/H2SessionFactoryFactory.kt create mode 100644 node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/PostgresSessionFactoryFactory.kt create mode 100644 node-api/src/main/resources/META-INF/services/net.corda.nodeapi.internal.persistence.factory.CordaSessionFactoryFactory create mode 100644 node-api/src/test/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfigurationFactoryLoadingTest.kt diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt index 0fca5b23bd..01bd86b7d1 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfiguration.kt @@ -5,24 +5,15 @@ import net.corda.core.internal.NamedCacheFactory import net.corda.core.internal.castIfPossible import net.corda.core.schemas.MappedSchema import net.corda.core.utilities.contextLogger -import net.corda.core.utilities.toHexString +import net.corda.nodeapi.internal.persistence.factory.CordaSessionFactoryFactory import org.hibernate.SessionFactory import org.hibernate.boot.Metadata import org.hibernate.boot.MetadataBuilder -import org.hibernate.boot.MetadataSources -import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder -import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService -import org.hibernate.cfg.Configuration import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider import org.hibernate.service.UnknownUnwrapTypeException -import org.hibernate.type.AbstractSingleColumnStandardBasicType -import org.hibernate.type.MaterializedBlobType -import org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor -import org.hibernate.type.descriptor.sql.BlobTypeDescriptor -import org.hibernate.type.descriptor.sql.VarbinaryTypeDescriptor import java.lang.management.ManagementFactory import java.sql.Connection +import java.util.ServiceLoader import javax.management.ObjectName import javax.persistence.AttributeConverter @@ -30,35 +21,38 @@ class HibernateConfiguration( schemas: Set, private val databaseConfig: DatabaseConfig, private val attributeConverters: Collection>, - private val jdbcUrl: String, + jdbcUrl: String, cacheFactory: NamedCacheFactory, val customClassLoader: ClassLoader? = null ) { companion object { private val logger = contextLogger() - // register custom converters - fun buildHibernateMetadata(metadataBuilder: MetadataBuilder, jdbcUrl:String, attributeConverters: Collection>): Metadata { - metadataBuilder.run { - attributeConverters.forEach { applyAttributeConverter(it) } - // Register a tweaked version of `org.hibernate.type.MaterializedBlobType` that truncates logged messages. - // to avoid OOM when large blobs might get logged. - applyBasicType(CordaMaterializedBlobType, CordaMaterializedBlobType.name) - applyBasicType(CordaWrapperBinaryType, CordaWrapperBinaryType.name) + // Will be used in open core + fun buildHibernateMetadata(metadataBuilder: MetadataBuilder, jdbcUrl: String, attributeConverters: Collection>): Metadata { + val sff = findSessionFactoryFactory(jdbcUrl, null) + return sff.buildHibernateMetadata(metadataBuilder, attributeConverters) + } - // Create a custom type that will map a blob to byteA in postgres and as a normal blob for all other dbms. - // This is required for the Checkpoints as a workaround for the issue that postgres has on azure. - if (jdbcUrl.contains(":postgresql:", ignoreCase = true)) { - applyBasicType(MapBlobToPostgresByteA, MapBlobToPostgresByteA.name) - } else { - applyBasicType(MapBlobToNormalBlob, MapBlobToNormalBlob.name) - } + private fun findSessionFactoryFactory(jdbcUrl: String, customClassLoader: ClassLoader?): CordaSessionFactoryFactory { + val serviceLoader = if (customClassLoader != null) + ServiceLoader.load(CordaSessionFactoryFactory::class.java, customClassLoader) + else + ServiceLoader.load(CordaSessionFactoryFactory::class.java) - return build() + val sessionFactories = serviceLoader.filter { it.canHandleDatabase(jdbcUrl) } + when (sessionFactories.size) { + 0 -> throw HibernateConfigException("Failed to find a SessionFactoryFactory to handle $jdbcUrl " + + "- factories present for ${serviceLoader.map { it.databaseType }}") + 1 -> return sessionFactories.single() + else -> throw HibernateConfigException("Found several SessionFactoryFactory classes to handle $jdbcUrl " + + "- classes ${sessionFactories.map { it.javaClass.canonicalName }}") } } } + val sessionFactoryFactory = findSessionFactoryFactory(jdbcUrl, customClassLoader) + private val sessionFactories = cacheFactory.buildNamed, SessionFactory>(Caffeine.newBuilder(), "HibernateConfiguration_sessionFactories") val sessionFactoryForRegisteredSchemas = schemas.let { @@ -70,35 +64,7 @@ class HibernateConfiguration( fun sessionFactoryForSchemas(key: Set): SessionFactory = sessionFactories.get(key, ::makeSessionFactoryForSchemas)!! private fun makeSessionFactoryForSchemas(schemas: Set): SessionFactory { - logger.info("Creating session factory for schemas: $schemas") - val serviceRegistry = BootstrapServiceRegistryBuilder().build() - val metadataSources = MetadataSources(serviceRegistry) - - val hbm2dll: String = - if(databaseConfig.initialiseSchema && databaseConfig.initialiseAppSchema == SchemaInitializationType.UPDATE) { - "update" - } else if((!databaseConfig.initialiseSchema && databaseConfig.initialiseAppSchema == SchemaInitializationType.UPDATE) - || databaseConfig.initialiseAppSchema == SchemaInitializationType.VALIDATE) { - "validate" - } else { - "none" - } - - // We set a connection provider as the auto schema generation requires it. The auto schema generation will not - // necessarily remain and would likely be replaced by something like Liquibase. For now it is very convenient though. - val config = Configuration(metadataSources).setProperty("hibernate.connection.provider_class", NodeDatabaseConnectionProvider::class.java.name) - .setProperty("hibernate.format_sql", "true") - .setProperty("hibernate.hbm2ddl.auto", hbm2dll) - .setProperty("javax.persistence.validation.mode", "none") - .setProperty("hibernate.connection.isolation", databaseConfig.transactionIsolationLevel.jdbcValue.toString()) - - schemas.forEach { schema -> - // TODO: require mechanism to set schemaOptions (databaseSchema, tablePrefix) which are not global to session - schema.mappedTypes.forEach { config.addAnnotatedClass(it) } - } - - val sessionFactory = buildSessionFactory(config, metadataSources, customClassLoader) - logger.info("Created session factory for schemas: $schemas") + val sessionFactory = sessionFactoryFactory.makeSessionFactoryForSchemas(databaseConfig, schemas, customClassLoader, attributeConverters) // export Hibernate JMX statistics if (databaseConfig.exportHibernateJMXStatistics) @@ -123,27 +89,6 @@ class HibernateConfiguration( } } - private fun buildSessionFactory(config: Configuration, metadataSources: MetadataSources, customClassLoader: ClassLoader?): SessionFactory { - config.standardServiceRegistryBuilder.applySettings(config.properties) - - if (customClassLoader != null) { - config.standardServiceRegistryBuilder.addService( - ClassLoaderService::class.java, - ClassLoaderServiceImpl(customClassLoader)) - } - - @Suppress("DEPRECATION") - val metadataBuilder = metadataSources.getMetadataBuilder(config.standardServiceRegistryBuilder.build()) - val metadata = buildHibernateMetadata(metadataBuilder, jdbcUrl, attributeConverters) - return metadata.sessionFactoryBuilder.run { - allowOutOfTransactionUpdateOperations(true) - applySecondLevelCacheSupport(false) - applyQueryCacheSupport(false) - enableReleaseResourcesOnCloseEnabled(true) - build() - } - } - // Supply Hibernate with connections from our underlying Exposed database integration. Only used // during schema creation / update. class NodeDatabaseConnectionProvider : ConnectionProvider { @@ -168,55 +113,5 @@ class HibernateConfiguration( override fun isUnwrappableAs(unwrapType: Class<*>?): Boolean = unwrapType == NodeDatabaseConnectionProvider::class.java } - // A tweaked version of `org.hibernate.type.MaterializedBlobType` that truncates logged messages. Also logs in hex. - object CordaMaterializedBlobType : AbstractSingleColumnStandardBasicType(BlobTypeDescriptor.DEFAULT, CordaPrimitiveByteArrayTypeDescriptor) { - override fun getName(): String { - return "materialized_blob" - } - } - - // A tweaked version of `org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor` that truncates logged messages. - private object CordaPrimitiveByteArrayTypeDescriptor : PrimitiveByteArrayTypeDescriptor() { - private const val LOG_SIZE_LIMIT = 1024 - - override fun extractLoggableRepresentation(value: ByteArray?): String { - return if (value == null) { - super.extractLoggableRepresentation(value) - } else { - if (value.size <= LOG_SIZE_LIMIT) { - "[size=${value.size}, value=${value.toHexString()}]" - } else { - "[size=${value.size}, value=${value.copyOfRange(0, LOG_SIZE_LIMIT).toHexString()}...truncated...]" - } - } - } - } - - // A tweaked version of `org.hibernate.type.WrapperBinaryType` that deals with ByteArray (java primitive byte[] type). - object CordaWrapperBinaryType : AbstractSingleColumnStandardBasicType(VarbinaryTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE) { - override fun getRegistrationKeys(): Array { - return arrayOf(name, "ByteArray", ByteArray::class.java.name) - } - - override fun getName(): String { - return "corda-wrapper-binary" - } - } - - // Maps to a byte array on postgres. - object MapBlobToPostgresByteA : AbstractSingleColumnStandardBasicType(VarbinaryTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE) { - override fun getRegistrationKeys(): Array { - return arrayOf(name, "ByteArray", ByteArray::class.java.name) - } - - override fun getName(): String { - return "corda-blob" - } - } - - object MapBlobToNormalBlob : MaterializedBlobType() { - override fun getName(): String { - return "corda-blob" - } - } + fun getExtraConfiguration(key: String ) = sessionFactoryFactory.getExtraConfiguration(key) } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/BaseSessionFactoryFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/BaseSessionFactoryFactory.kt new file mode 100644 index 0000000000..ee84479c41 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/BaseSessionFactoryFactory.kt @@ -0,0 +1,138 @@ +package net.corda.nodeapi.internal.persistence.factory + +import net.corda.core.schemas.MappedSchema +import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.toHexString +import net.corda.nodeapi.internal.persistence.DatabaseConfig +import net.corda.nodeapi.internal.persistence.HibernateConfiguration +import org.hibernate.SessionFactory +import org.hibernate.boot.Metadata +import org.hibernate.boot.MetadataBuilder +import org.hibernate.boot.MetadataSources +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder +import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService +import org.hibernate.cfg.Configuration +import org.hibernate.type.AbstractSingleColumnStandardBasicType +import org.hibernate.type.MaterializedBlobType +import org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor +import org.hibernate.type.descriptor.sql.BlobTypeDescriptor +import org.hibernate.type.descriptor.sql.VarbinaryTypeDescriptor +import javax.persistence.AttributeConverter + +abstract class BaseSessionFactoryFactory : CordaSessionFactoryFactory { + companion object { + private val logger = contextLogger() + } + + open fun buildHibernateConfig(databaseConfig: DatabaseConfig, metadataSources: MetadataSources): Configuration { + // We set a connection provider as the auto schema generation requires it. The auto schema generation will not + // necessarily remain and would likely be replaced by something like Liquibase. For now it is very convenient though. + return Configuration(metadataSources).setProperty("hibernate.connection.provider_class", HibernateConfiguration.NodeDatabaseConnectionProvider::class.java.name) + .setProperty("hibernate.format_sql", "true") + .setProperty("javax.persistence.validation.mode", "none") + .setProperty("hibernate.connection.isolation", databaseConfig.transactionIsolationLevel.jdbcValue.toString()) + .setProperty("hibernate.hbm2ddl.auto", "validate") + } + + override fun buildHibernateMetadata(metadataBuilder: MetadataBuilder, attributeConverters: Collection>): Metadata { + return metadataBuilder.run { + attributeConverters.forEach { applyAttributeConverter(it) } + // Register a tweaked version of `org.hibernate.type.MaterializedBlobType` that truncates logged messages. + // to avoid OOM when large blobs might get logged. + applyBasicType(CordaMaterializedBlobType, CordaMaterializedBlobType.name) + applyBasicType(CordaWrapperBinaryType, CordaWrapperBinaryType.name) + applyBasicType(MapBlobToNormalBlob, MapBlobToNormalBlob.name) + + build() + } + } + + fun buildSessionFactory( + config: Configuration, + metadataSources: MetadataSources, + customClassLoader: ClassLoader?, + attributeConverters: Collection>): SessionFactory { + config.standardServiceRegistryBuilder.applySettings(config.properties) + + if (customClassLoader != null) { + config.standardServiceRegistryBuilder.addService( + ClassLoaderService::class.java, + ClassLoaderServiceImpl(customClassLoader)) + } + + @Suppress("DEPRECATION") + val metadataBuilder = metadataSources.getMetadataBuilder(config.standardServiceRegistryBuilder.build()) + val metadata = buildHibernateMetadata(metadataBuilder, attributeConverters) + return metadata.sessionFactoryBuilder.run { + allowOutOfTransactionUpdateOperations(true) + applySecondLevelCacheSupport(false) + applyQueryCacheSupport(false) + enableReleaseResourcesOnCloseEnabled(true) + build() + } + } + + final override fun makeSessionFactoryForSchemas( + databaseConfig: DatabaseConfig, + schemas: Set, + customClassLoader: ClassLoader?, + attributeConverters: Collection>): SessionFactory { + logger.info("Creating session factory for schemas: $schemas") + val serviceRegistry = BootstrapServiceRegistryBuilder().build() + val metadataSources = MetadataSources(serviceRegistry) + + val config = buildHibernateConfig(databaseConfig, metadataSources) + schemas.forEach { schema -> + schema.mappedTypes.forEach { config.addAnnotatedClass(it) } + } + val sessionFactory = buildSessionFactory(config, metadataSources, customClassLoader, attributeConverters) + logger.info("Created session factory for schemas: $schemas") + return sessionFactory + } + + override fun getExtraConfiguration(key: String): Any? { + return null + } + + // A tweaked version of `org.hibernate.type.WrapperBinaryType` that deals with ByteArray (java primitive byte[] type). + object CordaWrapperBinaryType : AbstractSingleColumnStandardBasicType(VarbinaryTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE) { + override fun getRegistrationKeys(): Array { + return arrayOf(name, "ByteArray", ByteArray::class.java.name) + } + + override fun getName(): String { + return "corda-wrapper-binary" + } + } + + object MapBlobToNormalBlob : MaterializedBlobType() { + override fun getName(): String { + return "corda-blob" + } + } + + // A tweaked version of `org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor` that truncates logged messages. + private object CordaPrimitiveByteArrayTypeDescriptor : PrimitiveByteArrayTypeDescriptor() { + private const val LOG_SIZE_LIMIT = 1024 + + override fun extractLoggableRepresentation(value: ByteArray?): String { + return if (value == null) { + super.extractLoggableRepresentation(value) + } else { + if (value.size <= LOG_SIZE_LIMIT) { + "[size=${value.size}, value=${value.toHexString()}]" + } else { + "[size=${value.size}, value=${value.copyOfRange(0, LOG_SIZE_LIMIT).toHexString()}...truncated...]" + } + } + } + } + + // A tweaked version of `org.hibernate.type.MaterializedBlobType` that truncates logged messages. Also logs in hex. + object CordaMaterializedBlobType : AbstractSingleColumnStandardBasicType(BlobTypeDescriptor.DEFAULT, CordaPrimitiveByteArrayTypeDescriptor) { + override fun getName(): String { + return "materialized_blob" + } + } +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/CordaSessionFactoryFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/CordaSessionFactoryFactory.kt new file mode 100644 index 0000000000..c55b0bf4db --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/CordaSessionFactoryFactory.kt @@ -0,0 +1,20 @@ +package net.corda.nodeapi.internal.persistence.factory + +import net.corda.core.schemas.MappedSchema +import net.corda.nodeapi.internal.persistence.DatabaseConfig +import org.hibernate.SessionFactory +import org.hibernate.boot.Metadata +import org.hibernate.boot.MetadataBuilder +import javax.persistence.AttributeConverter + +interface CordaSessionFactoryFactory { + val databaseType: String + fun canHandleDatabase(jdbcUrl: String): Boolean + fun makeSessionFactoryForSchemas( + databaseConfig: DatabaseConfig, + schemas: Set, + customClassLoader: ClassLoader?, + attributeConverters: Collection>): SessionFactory + fun getExtraConfiguration(key: String): Any? + fun buildHibernateMetadata(metadataBuilder: MetadataBuilder, attributeConverters: Collection>): Metadata +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/H2SessionFactoryFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/H2SessionFactoryFactory.kt new file mode 100644 index 0000000000..d58e5b62b9 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/H2SessionFactoryFactory.kt @@ -0,0 +1,27 @@ +package net.corda.nodeapi.internal.persistence.factory + +import net.corda.nodeapi.internal.persistence.DatabaseConfig +import net.corda.nodeapi.internal.persistence.SchemaInitializationType +import org.hibernate.boot.MetadataSources +import org.hibernate.cfg.Configuration + +class H2SessionFactoryFactory : BaseSessionFactoryFactory() { + override fun canHandleDatabase(jdbcUrl: String): Boolean = jdbcUrl.startsWith("jdbc:h2:") + override val databaseType: String = "H2" + + override fun buildHibernateConfig(databaseConfig: DatabaseConfig, metadataSources: MetadataSources): Configuration { + val config = super.buildHibernateConfig(databaseConfig, metadataSources) + + val hbm2dll: String = + if (databaseConfig.initialiseSchema && databaseConfig.initialiseAppSchema == SchemaInitializationType.UPDATE) { + "update" + } else if ((!databaseConfig.initialiseSchema && databaseConfig.initialiseAppSchema == SchemaInitializationType.UPDATE) + || databaseConfig.initialiseAppSchema == SchemaInitializationType.VALIDATE) { + "validate" + } else { + "none" + } + config.setProperty("hibernate.hbm2ddl.auto", hbm2dll) + return config + } +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/PostgresSessionFactoryFactory.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/PostgresSessionFactoryFactory.kt new file mode 100644 index 0000000000..9d68bb2124 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/factory/PostgresSessionFactoryFactory.kt @@ -0,0 +1,41 @@ +package net.corda.nodeapi.internal.persistence.factory + +import org.hibernate.boot.Metadata +import org.hibernate.boot.MetadataBuilder +import org.hibernate.type.AbstractSingleColumnStandardBasicType +import org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor +import org.hibernate.type.descriptor.sql.VarbinaryTypeDescriptor +import javax.persistence.AttributeConverter + +class PostgresSessionFactoryFactory : BaseSessionFactoryFactory() { + override fun buildHibernateMetadata(metadataBuilder: MetadataBuilder, attributeConverters: Collection>): Metadata { + return metadataBuilder.run { + attributeConverters.forEach { applyAttributeConverter(it) } + // Register a tweaked version of `org.hibernate.type.MaterializedBlobType` that truncates logged messages. + // to avoid OOM when large blobs might get logged. + applyBasicType(CordaMaterializedBlobType, CordaMaterializedBlobType.name) + applyBasicType(CordaWrapperBinaryType, CordaWrapperBinaryType.name) + + // Create a custom type that will map a blob to byteA in postgres + // This is required for the Checkpoints as a workaround for the issue that postgres has on azure. + applyBasicType(MapBlobToPostgresByteA, MapBlobToPostgresByteA.name) + + build() + } + } + + override fun canHandleDatabase(jdbcUrl: String): Boolean = jdbcUrl.contains(":postgresql:") + + // Maps to a byte array on postgres. + object MapBlobToPostgresByteA : AbstractSingleColumnStandardBasicType(VarbinaryTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE) { + override fun getRegistrationKeys(): Array { + return arrayOf(name, "ByteArray", ByteArray::class.java.name) + } + + override fun getName(): String { + return "corda-blob" + } + } + + override val databaseType: String = "PostgreSQL" +} \ No newline at end of file diff --git a/node-api/src/main/resources/META-INF/services/net.corda.nodeapi.internal.persistence.factory.CordaSessionFactoryFactory b/node-api/src/main/resources/META-INF/services/net.corda.nodeapi.internal.persistence.factory.CordaSessionFactoryFactory new file mode 100644 index 0000000000..ffe029cd72 --- /dev/null +++ b/node-api/src/main/resources/META-INF/services/net.corda.nodeapi.internal.persistence.factory.CordaSessionFactoryFactory @@ -0,0 +1,2 @@ +net.corda.nodeapi.internal.persistence.factory.H2SessionFactoryFactory +net.corda.nodeapi.internal.persistence.factory.PostgresSessionFactoryFactory \ No newline at end of file diff --git a/node-api/src/test/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfigurationFactoryLoadingTest.kt b/node-api/src/test/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfigurationFactoryLoadingTest.kt new file mode 100644 index 0000000000..b7ad25a8d9 --- /dev/null +++ b/node-api/src/test/kotlin/net/corda/nodeapi/internal/persistence/HibernateConfigurationFactoryLoadingTest.kt @@ -0,0 +1,26 @@ +package net.corda.nodeapi.internal.persistence + +import com.nhaarman.mockito_kotlin.mock +import net.corda.core.internal.NamedCacheFactory +import org.junit.Assert +import org.junit.Test + +class HibernateConfigurationFactoryLoadingTest { + @Test(timeout=300_000) + fun checkErrorMessageForMissingFactory() { + val jdbcUrl = "jdbc:madeUpNonense:foobar.com:1234" + val presentFactories = listOf("H2", "PostgreSQL") + try { + val cacheFactory = mock() + HibernateConfiguration( + emptySet(), + DatabaseConfig(), + emptyList(), + jdbcUrl, + cacheFactory) + Assert.fail("Expected exception not thrown") + } catch (e: HibernateConfigException) { + Assert.assertEquals("Failed to find a SessionFactoryFactory to handle $jdbcUrl - factories present for ${presentFactories}", e.message) + } + } +} \ No newline at end of file