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
: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

View File

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

View File

@ -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.*

View File

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

View File

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

View File

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

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.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();

View File

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

View File

@ -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()

View File

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

View File

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

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.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.*

View File

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