mirror of
https://github.com/corda/corda.git
synced 2025-01-17 10:20:02 +00:00
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.
This commit is contained in:
parent
678fb6eb94
commit
ccca605865
@ -5,24 +5,15 @@ import net.corda.core.internal.NamedCacheFactory
|
|||||||
import net.corda.core.internal.castIfPossible
|
import net.corda.core.internal.castIfPossible
|
||||||
import net.corda.core.schemas.MappedSchema
|
import net.corda.core.schemas.MappedSchema
|
||||||
import net.corda.core.utilities.contextLogger
|
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.SessionFactory
|
||||||
import org.hibernate.boot.Metadata
|
import org.hibernate.boot.Metadata
|
||||||
import org.hibernate.boot.MetadataBuilder
|
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.engine.jdbc.connections.spi.ConnectionProvider
|
||||||
import org.hibernate.service.UnknownUnwrapTypeException
|
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.lang.management.ManagementFactory
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
|
import java.util.ServiceLoader
|
||||||
import javax.management.ObjectName
|
import javax.management.ObjectName
|
||||||
import javax.persistence.AttributeConverter
|
import javax.persistence.AttributeConverter
|
||||||
|
|
||||||
@ -30,35 +21,38 @@ class HibernateConfiguration(
|
|||||||
schemas: Set<MappedSchema>,
|
schemas: Set<MappedSchema>,
|
||||||
private val databaseConfig: DatabaseConfig,
|
private val databaseConfig: DatabaseConfig,
|
||||||
private val attributeConverters: Collection<AttributeConverter<*, *>>,
|
private val attributeConverters: Collection<AttributeConverter<*, *>>,
|
||||||
private val jdbcUrl: String,
|
jdbcUrl: String,
|
||||||
cacheFactory: NamedCacheFactory,
|
cacheFactory: NamedCacheFactory,
|
||||||
val customClassLoader: ClassLoader? = null
|
val customClassLoader: ClassLoader? = null
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = contextLogger()
|
private val logger = contextLogger()
|
||||||
|
|
||||||
// register custom converters
|
// Will be used in open core
|
||||||
fun buildHibernateMetadata(metadataBuilder: MetadataBuilder, jdbcUrl:String, attributeConverters: Collection<AttributeConverter<*, *>>): Metadata {
|
fun buildHibernateMetadata(metadataBuilder: MetadataBuilder, jdbcUrl: String, attributeConverters: Collection<AttributeConverter<*, *>>): Metadata {
|
||||||
metadataBuilder.run {
|
val sff = findSessionFactoryFactory(jdbcUrl, null)
|
||||||
attributeConverters.forEach { applyAttributeConverter(it) }
|
return sff.buildHibernateMetadata(metadataBuilder, attributeConverters)
|
||||||
// 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 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return build()
|
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)
|
||||||
|
|
||||||
|
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<Set<MappedSchema>, SessionFactory>(Caffeine.newBuilder(), "HibernateConfiguration_sessionFactories")
|
private val sessionFactories = cacheFactory.buildNamed<Set<MappedSchema>, SessionFactory>(Caffeine.newBuilder(), "HibernateConfiguration_sessionFactories")
|
||||||
|
|
||||||
val sessionFactoryForRegisteredSchemas = schemas.let {
|
val sessionFactoryForRegisteredSchemas = schemas.let {
|
||||||
@ -70,35 +64,7 @@ class HibernateConfiguration(
|
|||||||
fun sessionFactoryForSchemas(key: Set<MappedSchema>): SessionFactory = sessionFactories.get(key, ::makeSessionFactoryForSchemas)!!
|
fun sessionFactoryForSchemas(key: Set<MappedSchema>): SessionFactory = sessionFactories.get(key, ::makeSessionFactoryForSchemas)!!
|
||||||
|
|
||||||
private fun makeSessionFactoryForSchemas(schemas: Set<MappedSchema>): SessionFactory {
|
private fun makeSessionFactoryForSchemas(schemas: Set<MappedSchema>): SessionFactory {
|
||||||
logger.info("Creating session factory for schemas: $schemas")
|
val sessionFactory = sessionFactoryFactory.makeSessionFactoryForSchemas(databaseConfig, schemas, customClassLoader, attributeConverters)
|
||||||
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")
|
|
||||||
|
|
||||||
// export Hibernate JMX statistics
|
// export Hibernate JMX statistics
|
||||||
if (databaseConfig.exportHibernateJMXStatistics)
|
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
|
// Supply Hibernate with connections from our underlying Exposed database integration. Only used
|
||||||
// during schema creation / update.
|
// during schema creation / update.
|
||||||
class NodeDatabaseConnectionProvider : ConnectionProvider {
|
class NodeDatabaseConnectionProvider : ConnectionProvider {
|
||||||
@ -168,55 +113,5 @@ class HibernateConfiguration(
|
|||||||
override fun isUnwrappableAs(unwrapType: Class<*>?): Boolean = unwrapType == NodeDatabaseConnectionProvider::class.java
|
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.
|
fun getExtraConfiguration(key: String ) = sessionFactoryFactory.getExtraConfiguration(key)
|
||||||
object CordaMaterializedBlobType : AbstractSingleColumnStandardBasicType<ByteArray>(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<ByteArray>(VarbinaryTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE) {
|
|
||||||
override fun getRegistrationKeys(): Array<String> {
|
|
||||||
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<ByteArray>(VarbinaryTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE) {
|
|
||||||
override fun getRegistrationKeys(): Array<String> {
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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<AttributeConverter<*, *>>): 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<AttributeConverter<*, *>>): 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<MappedSchema>,
|
||||||
|
customClassLoader: ClassLoader?,
|
||||||
|
attributeConverters: Collection<AttributeConverter<*, *>>): 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<ByteArray>(VarbinaryTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE) {
|
||||||
|
override fun getRegistrationKeys(): Array<String> {
|
||||||
|
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<ByteArray>(BlobTypeDescriptor.DEFAULT, CordaPrimitiveByteArrayTypeDescriptor) {
|
||||||
|
override fun getName(): String {
|
||||||
|
return "materialized_blob"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<MappedSchema>,
|
||||||
|
customClassLoader: ClassLoader?,
|
||||||
|
attributeConverters: Collection<AttributeConverter<*, *>>): SessionFactory
|
||||||
|
fun getExtraConfiguration(key: String): Any?
|
||||||
|
fun buildHibernateMetadata(metadataBuilder: MetadataBuilder, attributeConverters: Collection<AttributeConverter<*, *>>): Metadata
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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<AttributeConverter<*, *>>): 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<ByteArray>(VarbinaryTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE) {
|
||||||
|
override fun getRegistrationKeys(): Array<String> {
|
||||||
|
return arrayOf(name, "ByteArray", ByteArray::class.java.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getName(): String {
|
||||||
|
return "corda-blob"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val databaseType: String = "PostgreSQL"
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
net.corda.nodeapi.internal.persistence.factory.H2SessionFactoryFactory
|
||||||
|
net.corda.nodeapi.internal.persistence.factory.PostgresSessionFactoryFactory
|
@ -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<NamedCacheFactory>()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user