diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt index 5e1017a6be..a14dec14d2 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt @@ -296,4 +296,6 @@ private fun Throwable.hasSQLExceptionCause(): Boolean = null -> false is SQLException -> true else -> cause?.hasSQLExceptionCause() ?: false - } \ No newline at end of file + } + +class CouldNotCreateDataSourceException(override val message: String?, override val cause: Throwable? = null) : Exception() \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 29496a4300..0c1b16c80c 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -13,6 +13,7 @@ package net.corda.node.internal import com.codahale.metrics.MetricRegistry import com.google.common.collect.MutableClassToInstanceMap import com.google.common.util.concurrent.MoreExecutors +import com.zaxxer.hikari.pool.HikariPool import net.corda.confidential.SwapIdentitiesFlow import net.corda.confidential.SwapIdentitiesHandler import net.corda.core.CordaException @@ -109,6 +110,7 @@ import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.persistence.CordaPersistence +import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.HibernateConfiguration import net.corda.nodeapi.internal.persistence.SchemaMigration @@ -267,7 +269,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, networkMapCache.clearNetworkMapCache() } } - + open fun start(): StartedNode { check(started == null) { "Node has already been started" } if (configuration.devMode) { @@ -1065,6 +1067,11 @@ class ConfigurationException(message: String) : CordaException(message) */ internal class NetworkMapCacheEmptyException : Exception() +/** + * Creates the connection pool to the database. + * + *@throws [CouldNotCreateDataSourceException] + */ fun configureDatabase(hikariProperties: Properties, databaseConfig: DatabaseConfig, wellKnownPartyFromX500Name: (CordaX500Name) -> Party?, @@ -1075,13 +1082,21 @@ fun configureDatabase(hikariProperties: Properties, // so we end up providing both descriptor and converter. We should re-examine this in later versions to see if // either Hibernate can be convinced to stop warning, use the descriptor by default, or something else. JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous)) - val dataSource = DataSourceFactory.createDataSource(hikariProperties) - val attributeConverters = listOf(AbstractPartyToX500NameAsStringConverter(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous)) - val jdbcUrl = hikariProperties.getProperty("dataSource.url", "") - SchemaMigration( - schemaService.schemaOptions.keys, - dataSource, - !isH2Database(jdbcUrl), - databaseConfig).nodeStartup() - return CordaPersistence(dataSource, databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, attributeConverters) + try { + val dataSource = DataSourceFactory.createDataSource(hikariProperties) + val attributeConverters = listOf(AbstractPartyToX500NameAsStringConverter(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous)) + val jdbcUrl = hikariProperties.getProperty("dataSource.url", "") + SchemaMigration( + schemaService.schemaOptions.keys, + dataSource, + !isH2Database(jdbcUrl), + databaseConfig).nodeStartup() + return CordaPersistence(dataSource, databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, attributeConverters) + } catch (ex: Exception) { + when { + ex is HikariPool.PoolInitializationException -> throw CouldNotCreateDataSourceException("Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database.") + ex.cause is ClassNotFoundException -> throw CouldNotCreateDataSourceException("Could not find the database driver class. Please add it to the 'drivers' folders. See: https://docs.corda.net/corda-configuration-file.html") + else -> throw CouldNotCreateDataSourceException("Could not create the DataSource: ${ex.message}", ex) + } + } } diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index a99a23eab2..0a08d28ddc 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -40,6 +40,7 @@ import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.config.UnknownConfigurationKeysException import net.corda.nodeapi.internal.persistence.DatabaseMigrationException import net.corda.nodeapi.internal.persistence.oracleJdbcDriverSerialFilter +import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException import net.corda.tools.shell.InteractiveShell import org.fusesource.jansi.Ansi import org.fusesource.jansi.AnsiConsole @@ -144,6 +145,9 @@ open class NodeStartup(val args: Array) { } catch (e: DatabaseMigrationException) { logger.error(e.message) return false + } catch (e: CouldNotCreateDataSourceException) { + logger.error(e.message, e.cause) + return false } catch (e: CheckpointIncompatibleException) { logger.error(e.message) return false diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/StaffedFlowHospital.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/StaffedFlowHospital.kt index 3f66c15c4b..eb7806e9b8 100644 --- a/node/src/main/kotlin/net/corda/node/services/statemachine/StaffedFlowHospital.kt +++ b/node/src/main/kotlin/net/corda/node/services/statemachine/StaffedFlowHospital.kt @@ -17,7 +17,7 @@ object StaffedFlowHospital : FlowHospital { private val patients = ConcurrentHashMap() - val numberOfPatients = patients.size + val numberOfPatients get() = patients.size class MedicalHistory { val records: MutableList = mutableListOf() diff --git a/node/src/test/kotlin/net/corda/node/services/statemachine/RetryFlowMockTest.kt b/node/src/test/kotlin/net/corda/node/services/statemachine/RetryFlowMockTest.kt index 710e545540..dbcd28796a 100644 --- a/node/src/test/kotlin/net/corda/node/services/statemachine/RetryFlowMockTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/statemachine/RetryFlowMockTest.kt @@ -89,8 +89,11 @@ class RetryFlowMockTest { @Test fun `Patient records do not leak in hospital`() { + val patientCountBefore = StaffedFlowHospital.numberOfPatients assertEquals(Unit, internalNodeA.startFlow(RetryFlow(1)).get()) - assertEquals(0, StaffedFlowHospital.numberOfPatients) + // Need to make sure the state machine has finished. Otherwise this test is flakey. + mockNet.waitQuiescent() + assertEquals(patientCountBefore, StaffedFlowHospital.numberOfPatients) assertEquals(2, RetryFlow.count) } }