mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
JPA Hibernate AbstractParty converter (#1205)
* Added JPA AbstractParty converter (using IdentityService to resolve anonymous parties). * Use partyFromX500Name. Add meaningful exception messages. * AutoApply the JPA AbstractParty converter. * Entity attribute still needs the Convert annotation. * Fix incorrect registration of custom attribute converter. * Deal with non-resolvable anonymous parties (eg. store as null and ignore) * Updates following PR review feedback. * Added documentation. * Added entry to changelog. * Added code documentation as per RN PR feedback request. * Updates required following rebase from master. * Renamed converter for clarity.
This commit is contained in:
parent
c8bbe453f5
commit
54cf46952c
@ -0,0 +1,30 @@
|
||||
package net.corda.core.schemas.converters
|
||||
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import javax.persistence.AttributeConverter
|
||||
import javax.persistence.Converter
|
||||
|
||||
/**
|
||||
* Converter to persist a party as its's well known identity (where resolvable)
|
||||
* Completely anonymous parties are stored as null (to preserve privacy)
|
||||
*/
|
||||
@Converter(autoApply = true)
|
||||
class AbstractPartyToX500NameAsStringConverter(val identitySvc: IdentityService) : AttributeConverter<AbstractParty, String> {
|
||||
|
||||
override fun convertToDatabaseColumn(party: AbstractParty?): String? {
|
||||
party?.let {
|
||||
return identitySvc.partyFromAnonymous(party)?.toString()
|
||||
}
|
||||
return null // non resolvable anonymous parties
|
||||
}
|
||||
|
||||
override fun convertToEntityAttribute(dbData: String?): AbstractParty? {
|
||||
dbData?.let {
|
||||
val party = identitySvc.partyFromX500Name(X500Name(dbData))
|
||||
return party as AbstractParty
|
||||
}
|
||||
return null // non resolvable anonymous parties are stored as nulls
|
||||
}
|
||||
}
|
@ -93,6 +93,13 @@ Several examples of entities and mappings are provided in the codebase, includin
|
||||
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/schemas/CashSchemaV1.kt
|
||||
:language: kotlin
|
||||
|
||||
Identity mapping
|
||||
----------------
|
||||
Schema entity attributes defined by identity types (``AbstractParty``, ``Party``, ``AnonymousParty``) are automatically
|
||||
processed to ensure only the ``X500Name`` of the identity is persisted where an identity is well known, otherwise a null
|
||||
value is stored in the associated column. To preserve privacy, identity keys are never persisted. Developers should use
|
||||
the ``IdentityService`` to resolve keys from well know X500 identity names.
|
||||
|
||||
JDBC session
|
||||
------------
|
||||
Apps may also interact directly with the underlying Node's database by using a standard
|
||||
|
@ -91,6 +91,9 @@ UNRELEASED
|
||||
of different soft locking retrieval behaviours (exclusive of soft locked states, soft locked states only, specified
|
||||
by set of lock ids)
|
||||
|
||||
* Added JPA ``AbstractPartyConverter`` to ensure identity schema attributes are persisted securely according to type
|
||||
(well known party, resolvable anonymous party, completely anonymous party).
|
||||
|
||||
Milestone 13
|
||||
------------
|
||||
|
||||
|
@ -6,11 +6,17 @@ import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.VaultQueryService
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.node.services.database.HibernateConfiguration
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.services.vault.HibernateVaultQueryImpl
|
||||
import net.corda.node.services.vault.NodeVaultService
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.testing.*
|
||||
|
@ -479,7 +479,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
private fun makeVaultObservers() {
|
||||
VaultSoftLockManager(services.vaultService, smm)
|
||||
ScheduledActivityObserver(services)
|
||||
HibernateObserver(services.vaultService.rawUpdates, HibernateConfiguration(services.schemaService, configuration.database ?: Properties()))
|
||||
HibernateObserver(services.vaultService.rawUpdates, HibernateConfiguration(services.schemaService, configuration.database ?: Properties(), services.identityService))
|
||||
}
|
||||
|
||||
private fun makeInfo(): NodeInfo {
|
||||
@ -766,7 +766,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
override val networkMapCache by lazy { InMemoryNetworkMapCache(this) }
|
||||
override val vaultService by lazy { NodeVaultService(this, configuration.dataSourceProperties, configuration.database) }
|
||||
override val vaultQueryService by lazy {
|
||||
HibernateVaultQueryImpl(HibernateConfiguration(schemaService, configuration.database ?: Properties()), vaultService.updatesPublisher)
|
||||
HibernateVaultQueryImpl(HibernateConfiguration(schemaService, configuration.database ?: Properties(), identityService), vaultService.updatesPublisher)
|
||||
}
|
||||
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
|
||||
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
|
||||
|
@ -1,7 +1,9 @@
|
||||
package net.corda.node.services.database
|
||||
|
||||
import net.corda.core.internal.castIfPossible
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.schemas.converters.AbstractPartyToX500NameAsStringConverter
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.services.api.SchemaService
|
||||
import net.corda.node.utilities.DatabaseTransactionManager
|
||||
@ -18,7 +20,7 @@ import java.sql.Connection
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class HibernateConfiguration(val schemaService: SchemaService, val databaseProperties: Properties) {
|
||||
class HibernateConfiguration(val schemaService: SchemaService, val databaseProperties: Properties, val identitySvc: IdentityService) {
|
||||
companion object {
|
||||
val logger = loggerFor<HibernateConfiguration>()
|
||||
}
|
||||
@ -58,6 +60,7 @@ class HibernateConfiguration(val schemaService: SchemaService, val databasePrope
|
||||
val config = Configuration(metadataSources).setProperty("hibernate.connection.provider_class", HibernateConfiguration.NodeDatabaseConnectionProvider::class.java.name)
|
||||
.setProperty("hibernate.hbm2ddl.auto", if (databaseProperties.getProperty("initDatabase","true") == "true") "update" else "validate")
|
||||
.setProperty("hibernate.format_sql", "true")
|
||||
|
||||
schemas.forEach { schema ->
|
||||
// TODO: require mechanism to set schemaOptions (databaseSchema, tablePrefix) which are not global to session
|
||||
schema.mappedTypes.forEach { config.addAnnotatedClass(it) }
|
||||
@ -76,6 +79,9 @@ class HibernateConfiguration(val schemaService: SchemaService, val databasePrope
|
||||
return Identifier.toIdentifier(tablePrefix + default.text, default.isQuoted)
|
||||
}
|
||||
})
|
||||
// register custom converters
|
||||
applyAttributeConverter(AbstractPartyToX500NameAsStringConverter(identitySvc))
|
||||
|
||||
build()
|
||||
}
|
||||
|
||||
|
@ -105,8 +105,8 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
|
||||
var participants: Set<CommonSchemaV1.Party>,
|
||||
|
||||
/** [OwnableState] attributes */
|
||||
@OneToOne(cascade = arrayOf(CascadeType.ALL))
|
||||
var owner: CommonSchemaV1.Party,
|
||||
@Column(name = "owner_id")
|
||||
var owner: AbstractParty,
|
||||
|
||||
/** [FungibleAsset] attributes
|
||||
*
|
||||
@ -126,7 +126,7 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
|
||||
var issuerRef: ByteArray
|
||||
) : PersistentState() {
|
||||
constructor(_owner: AbstractParty, _quantity: Long, _issuerParty: AbstractParty, _issuerRef: OpaqueBytes, _participants: List<AbstractParty>) :
|
||||
this(owner = CommonSchemaV1.Party(_owner),
|
||||
this(owner = _owner,
|
||||
quantity = _quantity,
|
||||
issuerParty = CommonSchemaV1.Party(_issuerParty),
|
||||
issuerRef = _issuerRef.bytes,
|
||||
|
@ -18,6 +18,7 @@ import net.corda.core.node.services.vault.QueryCriteria.VaultCustomQueryCriteria
|
||||
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria;
|
||||
import net.corda.core.utilities.OpaqueBytes;
|
||||
import net.corda.node.utilities.CordaPersistence;
|
||||
import net.corda.node.services.identity.*;
|
||||
import net.corda.schemas.CashSchemaV1;
|
||||
import net.corda.testing.TestConstants;
|
||||
import net.corda.testing.TestDependencyInjectionBase;
|
||||
@ -44,6 +45,7 @@ import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZ
|
||||
import static net.corda.core.utilities.ByteArrays.toHexString;
|
||||
import static net.corda.testing.CoreTestUtils.*;
|
||||
import static net.corda.testing.node.MockServicesKt.makeTestDatabaseAndMockServices;
|
||||
import static net.corda.testing.TestConstants.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class VaultQueryJavaTests extends TestDependencyInjectionBase {
|
||||
@ -57,6 +59,7 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
|
||||
public void setUp() {
|
||||
ArrayList<KeyPair> keys = new ArrayList<>();
|
||||
keys.add(getMEGA_CORP_KEY());
|
||||
InMemoryIdentityService identityService = new InMemoryIdentityService(getMOCK_IDENTITIES(), Collections.emptyMap(), getDUMMY_CA().getCertificate());
|
||||
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(Collections.EMPTY_SET, keys);
|
||||
database = databaseAndServices.getFirst();
|
||||
services = databaseAndServices.getSecond();
|
||||
|
@ -14,6 +14,7 @@ import net.corda.core.schemas.CommonSchemaV1
|
||||
import net.corda.core.schemas.PersistentStateRef
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.services.schema.HibernateObserver
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.services.vault.HibernateVaultQueryImpl
|
||||
@ -71,7 +72,8 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
|
||||
database = configureDatabase(dataSourceProps, defaultDatabaseProperties)
|
||||
val customSchemas = setOf(VaultSchemaV1, CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3)
|
||||
database.transaction {
|
||||
hibernateConfig = HibernateConfiguration(NodeSchemaService(customSchemas), makeTestDatabaseProperties())
|
||||
val identityService = InMemoryIdentityService(MOCK_IDENTITIES, trustRoot = DUMMY_CA.certificate)
|
||||
hibernateConfig = HibernateConfiguration(NodeSchemaService(customSchemas), makeTestDatabaseProperties(), identityService)
|
||||
services = object : MockServices(BOB_KEY) {
|
||||
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)
|
||||
|
||||
|
@ -10,9 +10,12 @@ import net.corda.core.schemas.QueryableState
|
||||
import net.corda.testing.LogHelper
|
||||
import net.corda.node.services.api.SchemaService
|
||||
import net.corda.node.services.database.HibernateConfiguration
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.testing.DUMMY_CA
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.MOCK_IDENTITIES
|
||||
import net.corda.testing.node.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.makeTestDatabaseProperties
|
||||
import org.hibernate.annotations.Cascade
|
||||
@ -102,7 +105,8 @@ class HibernateObserverTests {
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val observer = HibernateObserver(rawUpdatesPublisher, HibernateConfiguration(schemaService, makeTestDatabaseProperties()))
|
||||
val identityService = InMemoryIdentityService(MOCK_IDENTITIES, trustRoot = DUMMY_CA.certificate)
|
||||
val observer = HibernateObserver(rawUpdatesPublisher, HibernateConfiguration(schemaService, makeTestDatabaseProperties(), identityService))
|
||||
database.transaction {
|
||||
rawUpdatesPublisher.onNext(Vault.Update(emptySet(), setOf(StateAndRef(TransactionState(TestState(), MEGA_CORP), StateRef(SecureHash.sha256("dummy"), 0)))))
|
||||
val parentRowCountResult = TransactionManager.current().connection.prepareStatement("select count(*) from Parents").executeQuery()
|
||||
|
@ -23,6 +23,9 @@ import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.toNonEmptySet
|
||||
import net.corda.node.services.database.HibernateConfiguration
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.fillWithSomeTestCash
|
||||
|
@ -13,6 +13,14 @@ import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.node.services.vault.*
|
||||
import net.corda.core.node.services.vault.QueryCriteria.*
|
||||
import net.corda.core.utilities.seconds
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.toHexString
|
||||
import net.corda.node.services.database.HibernateConfiguration
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
|
@ -12,6 +12,9 @@ import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.node.services.database.HibernateConfiguration
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.*
|
||||
|
@ -88,7 +88,7 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub {
|
||||
|
||||
lateinit var hibernatePersister: HibernateObserver
|
||||
|
||||
fun makeVaultService(dataSourceProps: Properties, hibernateConfig: HibernateConfiguration = HibernateConfiguration(NodeSchemaService(), makeTestDatabaseProperties())): VaultService {
|
||||
fun makeVaultService(dataSourceProps: Properties, hibernateConfig: HibernateConfiguration = HibernateConfiguration(NodeSchemaService(), makeTestDatabaseProperties(), identityService)): VaultService {
|
||||
val vaultService = NodeVaultService(this, dataSourceProps, makeTestDatabaseProperties())
|
||||
hibernatePersister = HibernateObserver(vaultService.rawUpdates, hibernateConfig)
|
||||
return vaultService
|
||||
@ -221,7 +221,8 @@ fun makeTestDatabaseAndMockServices(customSchemas: Set<MappedSchema> = setOf(Com
|
||||
val databaseProperties = makeTestDatabaseProperties()
|
||||
val database = configureDatabase(dataSourceProps, databaseProperties)
|
||||
val mockService = database.transaction {
|
||||
val hibernateConfig = HibernateConfiguration(NodeSchemaService(customSchemas), databaseProperties)
|
||||
val identityService = InMemoryIdentityService(MOCK_IDENTITIES, trustRoot = DUMMY_CA.certificate)
|
||||
val hibernateConfig = HibernateConfiguration(NodeSchemaService(customSchemas), databaseProperties, identityService)
|
||||
object : MockServices(*(keys.toTypedArray())) {
|
||||
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user