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:
josecoll
2017-08-10 17:17:12 +01:00
committed by GitHub
parent c8bbe453f5
commit 54cf46952c
14 changed files with 86 additions and 10 deletions

View File

@ -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
}
}

View File

@ -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 .. literalinclude:: ../../finance/src/main/kotlin/net/corda/schemas/CashSchemaV1.kt
:language: kotlin :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 JDBC session
------------ ------------
Apps may also interact directly with the underlying Node's database by using a standard Apps may also interact directly with the underlying Node's database by using a standard

View File

@ -91,6 +91,9 @@ UNRELEASED
of different soft locking retrieval behaviours (exclusive of soft locked states, soft locked states only, specified of different soft locking retrieval behaviours (exclusive of soft locked states, soft locked states only, specified
by set of lock ids) 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 Milestone 13
------------ ------------

View File

@ -6,11 +6,17 @@ import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party 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.VaultService
import net.corda.core.node.services.queryBy import net.corda.core.node.services.queryBy
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes 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.services.vault.NodeVaultService
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
import net.corda.testing.* import net.corda.testing.*

View File

@ -479,7 +479,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
private fun makeVaultObservers() { private fun makeVaultObservers() {
VaultSoftLockManager(services.vaultService, smm) VaultSoftLockManager(services.vaultService, smm)
ScheduledActivityObserver(services) 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 { private fun makeInfo(): NodeInfo {
@ -766,7 +766,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
override val networkMapCache by lazy { InMemoryNetworkMapCache(this) } override val networkMapCache by lazy { InMemoryNetworkMapCache(this) }
override val vaultService by lazy { NodeVaultService(this, configuration.dataSourceProperties, configuration.database) } override val vaultService by lazy { NodeVaultService(this, configuration.dataSourceProperties, configuration.database) }
override val vaultQueryService by lazy { 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 // 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 // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with

View File

@ -1,7 +1,9 @@
package net.corda.node.services.database package net.corda.node.services.database
import net.corda.core.internal.castIfPossible import net.corda.core.internal.castIfPossible
import net.corda.core.node.services.IdentityService
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.converters.AbstractPartyToX500NameAsStringConverter
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.node.services.api.SchemaService import net.corda.node.services.api.SchemaService
import net.corda.node.utilities.DatabaseTransactionManager import net.corda.node.utilities.DatabaseTransactionManager
@ -18,7 +20,7 @@ import java.sql.Connection
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap 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 { companion object {
val logger = loggerFor<HibernateConfiguration>() 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) 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.hbm2ddl.auto", if (databaseProperties.getProperty("initDatabase","true") == "true") "update" else "validate")
.setProperty("hibernate.format_sql", "true") .setProperty("hibernate.format_sql", "true")
schemas.forEach { schema -> schemas.forEach { schema ->
// TODO: require mechanism to set schemaOptions (databaseSchema, tablePrefix) which are not global to session // TODO: require mechanism to set schemaOptions (databaseSchema, tablePrefix) which are not global to session
schema.mappedTypes.forEach { config.addAnnotatedClass(it) } 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) return Identifier.toIdentifier(tablePrefix + default.text, default.isQuoted)
} }
}) })
// register custom converters
applyAttributeConverter(AbstractPartyToX500NameAsStringConverter(identitySvc))
build() build()
} }

View File

@ -105,8 +105,8 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
var participants: Set<CommonSchemaV1.Party>, var participants: Set<CommonSchemaV1.Party>,
/** [OwnableState] attributes */ /** [OwnableState] attributes */
@OneToOne(cascade = arrayOf(CascadeType.ALL)) @Column(name = "owner_id")
var owner: CommonSchemaV1.Party, var owner: AbstractParty,
/** [FungibleAsset] attributes /** [FungibleAsset] attributes
* *
@ -126,7 +126,7 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
var issuerRef: ByteArray var issuerRef: ByteArray
) : PersistentState() { ) : PersistentState() {
constructor(_owner: AbstractParty, _quantity: Long, _issuerParty: AbstractParty, _issuerRef: OpaqueBytes, _participants: List<AbstractParty>) : constructor(_owner: AbstractParty, _quantity: Long, _issuerParty: AbstractParty, _issuerRef: OpaqueBytes, _participants: List<AbstractParty>) :
this(owner = CommonSchemaV1.Party(_owner), this(owner = _owner,
quantity = _quantity, quantity = _quantity,
issuerParty = CommonSchemaV1.Party(_issuerParty), issuerParty = CommonSchemaV1.Party(_issuerParty),
issuerRef = _issuerRef.bytes, issuerRef = _issuerRef.bytes,

View File

@ -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.node.services.vault.QueryCriteria.VaultQueryCriteria;
import net.corda.core.utilities.OpaqueBytes; import net.corda.core.utilities.OpaqueBytes;
import net.corda.node.utilities.CordaPersistence; import net.corda.node.utilities.CordaPersistence;
import net.corda.node.services.identity.*;
import net.corda.schemas.CashSchemaV1; import net.corda.schemas.CashSchemaV1;
import net.corda.testing.TestConstants; import net.corda.testing.TestConstants;
import net.corda.testing.TestDependencyInjectionBase; 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.core.utilities.ByteArrays.toHexString;
import static net.corda.testing.CoreTestUtils.*; import static net.corda.testing.CoreTestUtils.*;
import static net.corda.testing.node.MockServicesKt.makeTestDatabaseAndMockServices; import static net.corda.testing.node.MockServicesKt.makeTestDatabaseAndMockServices;
import static net.corda.testing.TestConstants.*;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
public class VaultQueryJavaTests extends TestDependencyInjectionBase { public class VaultQueryJavaTests extends TestDependencyInjectionBase {
@ -57,6 +59,7 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
public void setUp() { public void setUp() {
ArrayList<KeyPair> keys = new ArrayList<>(); ArrayList<KeyPair> keys = new ArrayList<>();
keys.add(getMEGA_CORP_KEY()); 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); Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(Collections.EMPTY_SET, keys);
database = databaseAndServices.getFirst(); database = databaseAndServices.getFirst();
services = databaseAndServices.getSecond(); services = databaseAndServices.getSecond();

View File

@ -14,6 +14,7 @@ import net.corda.core.schemas.CommonSchemaV1
import net.corda.core.schemas.PersistentStateRef import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.transactions.SignedTransaction 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.HibernateObserver
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.vault.HibernateVaultQueryImpl import net.corda.node.services.vault.HibernateVaultQueryImpl
@ -71,7 +72,8 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
database = configureDatabase(dataSourceProps, defaultDatabaseProperties) database = configureDatabase(dataSourceProps, defaultDatabaseProperties)
val customSchemas = setOf(VaultSchemaV1, CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3) val customSchemas = setOf(VaultSchemaV1, CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3)
database.transaction { 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) { services = object : MockServices(BOB_KEY) {
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig) override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)

View File

@ -10,9 +10,12 @@ import net.corda.core.schemas.QueryableState
import net.corda.testing.LogHelper import net.corda.testing.LogHelper
import net.corda.node.services.api.SchemaService import net.corda.node.services.api.SchemaService
import net.corda.node.services.database.HibernateConfiguration 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.CordaPersistence
import net.corda.node.utilities.configureDatabase import net.corda.node.utilities.configureDatabase
import net.corda.testing.DUMMY_CA
import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP
import net.corda.testing.MOCK_IDENTITIES
import net.corda.testing.node.makeTestDataSourceProperties import net.corda.testing.node.makeTestDataSourceProperties
import net.corda.testing.node.makeTestDatabaseProperties import net.corda.testing.node.makeTestDatabaseProperties
import org.hibernate.annotations.Cascade import org.hibernate.annotations.Cascade
@ -102,7 +105,8 @@ class HibernateObserverTests {
} }
@Suppress("UNUSED_VARIABLE") @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 { database.transaction {
rawUpdatesPublisher.onNext(Vault.Update(emptySet(), setOf(StateAndRef(TransactionState(TestState(), MEGA_CORP), StateRef(SecureHash.sha256("dummy"), 0))))) 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() val parentRowCountResult = TransactionManager.current().connection.prepareStatement("select count(*) from Parents").executeQuery()

View File

@ -23,6 +23,9 @@ import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toNonEmptySet 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.node.utilities.CordaPersistence
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.contracts.fillWithSomeTestCash import net.corda.testing.contracts.fillWithSomeTestCash

View File

@ -13,6 +13,14 @@ import net.corda.core.identity.Party
import net.corda.core.node.services.* import net.corda.core.node.services.*
import net.corda.core.node.services.vault.* import net.corda.core.node.services.vault.*
import net.corda.core.node.services.vault.QueryCriteria.* 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.core.utilities.*
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase import net.corda.node.utilities.configureDatabase

View File

@ -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
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
import net.corda.core.transactions.TransactionBuilder 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.node.utilities.CordaPersistence
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.contracts.* import net.corda.testing.contracts.*

View File

@ -88,7 +88,7 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub {
lateinit var hibernatePersister: HibernateObserver 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()) val vaultService = NodeVaultService(this, dataSourceProps, makeTestDatabaseProperties())
hibernatePersister = HibernateObserver(vaultService.rawUpdates, hibernateConfig) hibernatePersister = HibernateObserver(vaultService.rawUpdates, hibernateConfig)
return vaultService return vaultService
@ -221,7 +221,8 @@ fun makeTestDatabaseAndMockServices(customSchemas: Set<MappedSchema> = setOf(Com
val databaseProperties = makeTestDatabaseProperties() val databaseProperties = makeTestDatabaseProperties()
val database = configureDatabase(dataSourceProps, databaseProperties) val database = configureDatabase(dataSourceProps, databaseProperties)
val mockService = database.transaction { 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())) { object : MockServices(*(keys.toTypedArray())) {
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig) override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)