From 65ce5fec4bab82a26147b66323f2a0e5e52cb7dd Mon Sep 17 00:00:00 2001 From: josecoll Date: Mon, 17 Jul 2017 18:20:02 +0100 Subject: [PATCH] Fixed Vault Query over RPC using custom attributes. (#1066) * Implemented Kryo custom serializers for Field and KProperty types. * Adjusted KPropertySerializer to use kotlin member properties upon read() due to failing RPC tests. Added additional Kotlin and Java tests (CordaRPCClient, StandaaloneCordaRPCClient) Annotated schemas to be CordaSerializable (required when referencing as KProperty in custom queries). Cleanup some outstanding compiler warnings. * Added client RPC Java integration and smoke tests to build. * Clean up compiler warnings in Java tests. * Fixed incorrect assertion expectation. * Backed out Field and KProperty custom serializers. * Backed out Field and KProperty custom serializers. * Store VaultQueryCustom custom column references as name and class (from Java Field and Kotlin KProperty1 types respectively). Custom serialization of Field and KProperty type no longer required. * Removed blank lines as per RP review comments. --- client/rpc/build.gradle | 10 ++ .../client/rpc/CordaRPCJavaClientTest.java | 94 ++++++++++++++++++ .../corda/client/rpc/CordaRPCClientTest.kt | 32 ++++-- .../rpc/StandaloneCordaRPCJavaClientTest.java | 99 +++++++++++++++++++ .../kotlin/rpc/StandaloneCordaRPClientTest.kt | 48 +++++++-- .../node/services/vault/QueryCriteriaUtils.kt | 51 ++++------ .../net/corda/core/schemas/PersistentTypes.kt | 3 +- .../kotlin/net/corda/schemas/CashSchemaV1.kt | 2 + .../corda/schemas/CommercialPaperSchemaV1.kt | 2 + node/build.gradle | 10 ++ .../vault/HibernateQueryCriteriaParser.kt | 12 ++- .../corda/node/services/vault/VaultSchema.kt | 2 + 12 files changed, 315 insertions(+), 50 deletions(-) create mode 100644 client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java create mode 100644 client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java diff --git a/client/rpc/build.gradle b/client/rpc/build.gradle index b2ab10dff4..6ee29ab387 100644 --- a/client/rpc/build.gradle +++ b/client/rpc/build.gradle @@ -24,6 +24,11 @@ sourceSets { runtimeClasspath += main.output + test.output srcDir file('src/integration-test/kotlin') } + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integration-test/java') + } } smokeTest { kotlin { @@ -33,6 +38,11 @@ sourceSets { runtimeClasspath += main.output srcDir file('src/smoke-test/kotlin') } + java { + compileClasspath += main.output + runtimeClasspath += main.output + srcDir file('src/smoke-test/java') + } } } diff --git a/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java new file mode 100644 index 0000000000..a09af59e85 --- /dev/null +++ b/client/rpc/src/integration-test/java/net/corda/client/rpc/CordaRPCJavaClientTest.java @@ -0,0 +1,94 @@ +package net.corda.client.rpc; + +import com.google.common.util.concurrent.*; +import net.corda.client.rpc.internal.*; +import net.corda.contracts.asset.*; +import net.corda.core.contracts.*; +import net.corda.core.messaging.*; +import net.corda.core.node.services.*; +import net.corda.core.node.services.vault.*; +import net.corda.core.utilities.*; +import net.corda.flows.*; +import net.corda.node.internal.*; +import net.corda.node.services.transactions.*; +import net.corda.nodeapi.*; +import net.corda.schemas.*; +import net.corda.testing.node.*; +import org.junit.*; + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.*; + +import static kotlin.test.AssertionsKt.*; +import static net.corda.client.rpc.CordaRPCClientConfiguration.*; +import static net.corda.node.services.RPCUserServiceKt.*; +import static net.corda.testing.TestConstants.*; + +public class CordaRPCJavaClientTest extends NodeBasedTest { + private List perms = Arrays.asList(startFlowPermission(CashPaymentFlow.class), startFlowPermission(CashIssueFlow.class)); + private Set permSet = new HashSet<>(perms); + private User rpcUser = new User("user1", "test", permSet); + + private Node node; + private CordaRPCClient client; + private RPCClient.RPCConnection connection = null; + private CordaRPCOps rpcProxy; + + private void login(String username, String password) { + connection = client.start(username, password); + rpcProxy = connection.getProxy(); + } + + @Before + public void setUp() throws ExecutionException, InterruptedException { + Set services = new HashSet<>(Collections.singletonList(new ServiceInfo(ValidatingNotaryService.Companion.getType(), null))); + ListenableFuture nodeFuture = startNode(getALICE().getName(), 1, services, Arrays.asList(rpcUser), Collections.emptyMap()); + node = nodeFuture.get(); + client = new CordaRPCClient(node.getConfiguration().getRpcAddress(), null, getDefault()); + } + + @After + public void done() throws IOException { + connection.close(); + } + + @Test + public void testLogin() { + login(rpcUser.getUsername(), rpcUser.getPassword()); + } + + @Test + public void testCashBalances() throws NoSuchFieldException, ExecutionException, InterruptedException { + login(rpcUser.getUsername(), rpcUser.getPassword()); + + Amount dollars123 = new Amount<>(123, Currency.getInstance("USD")); + + FlowHandle flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class, + dollars123, OpaqueBytes.of("1".getBytes()), + node.info.getLegalIdentity(), node.info.getLegalIdentity()); + System.out.println("Started issuing cash, waiting on result"); + flowHandle.getReturnValue().get(); + + Amount balance = getBalance(Currency.getInstance("USD")); + System.out.print("Balance: " + balance + "\n"); + + assertEquals(dollars123, balance, "matching"); + } + + private Amount getBalance(Currency currency) throws NoSuchFieldException { + Field pennies = CashSchemaV1.PersistentCashState.class.getDeclaredField("pennies"); + @SuppressWarnings("unchecked") + QueryCriteria sumCriteria = new QueryCriteria.VaultCustomQueryCriteria(Builder.sum(pennies)); + + Vault.Page results = rpcProxy.vaultQueryByCriteria(sumCriteria, Cash.State.class); + if (results.getOtherResults().isEmpty()) { + return new Amount<>(0L, currency); + } else { + Assert.assertNotNull(results.getOtherResults()); + Long quantity = (Long) results.getOtherResults().get(0); + return new Amount<>(quantity, currency); + } + } +} diff --git a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt index 40a179fd29..2aa147d3a9 100644 --- a/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt +++ b/client/rpc/src/integration-test/kotlin/net/corda/client/rpc/CordaRPCClientTest.kt @@ -1,13 +1,17 @@ package net.corda.client.rpc +import net.corda.contracts.asset.Cash +import net.corda.core.contracts.Amount import net.corda.core.contracts.DOLLARS +import net.corda.core.contracts.USD +import net.corda.core.crypto.random63BitValue import net.corda.core.flows.FlowInitiator import net.corda.core.getOrThrow import net.corda.core.messaging.* import net.corda.core.node.services.ServiceInfo -import net.corda.core.crypto.random63BitValue +import net.corda.core.node.services.vault.QueryCriteria +import net.corda.core.node.services.vault.builder import net.corda.core.utilities.OpaqueBytes -import net.corda.testing.ALICE import net.corda.flows.CashException import net.corda.flows.CashIssueFlow import net.corda.flows.CashPaymentFlow @@ -15,10 +19,13 @@ import net.corda.node.internal.Node import net.corda.node.services.startFlowPermission import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.nodeapi.User +import net.corda.schemas.CashSchemaV1 +import net.corda.testing.ALICE import net.corda.testing.node.NodeBasedTest import org.apache.activemq.artemis.api.core.ActiveMQSecurityException import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.junit.After +import org.junit.Assert import org.junit.Before import org.junit.Test import java.util.* @@ -117,10 +124,23 @@ class CordaRPCClientTest : NodeBasedTest() { println("Started issuing cash, waiting on result") flowHandle.returnValue.get() - val finishCash = proxy.getCashBalances() - println("Cash Balances: $finishCash") - assertEquals(1, finishCash.size) - assertEquals(123.DOLLARS, finishCash.get(Currency.getInstance("USD"))) + val cashDollars = getBalance(USD, proxy) + println("Balance: $cashDollars") + assertEquals(123.DOLLARS, cashDollars) + } + + private fun getBalance(currency: Currency, proxy: CordaRPCOps): Amount { + val sum = builder { CashSchemaV1.PersistentCashState::pennies.sum() } + val sumCriteria = QueryCriteria.VaultCustomQueryCriteria(sum) + + val results = proxy.vaultQueryBy(sumCriteria) + if (results.otherResults.isEmpty()) { + return Amount(0L, currency) + } else { + Assert.assertNotNull(results.otherResults) + val quantity = results.otherResults[0] as Long + return Amount(quantity, currency) + } } @Test diff --git a/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java new file mode 100644 index 0000000000..8039adeadf --- /dev/null +++ b/client/rpc/src/smoke-test/java/net/corda/java/rpc/StandaloneCordaRPCJavaClientTest.java @@ -0,0 +1,99 @@ +package net.corda.java.rpc; + +import net.corda.client.rpc.*; +import net.corda.contracts.asset.*; +import net.corda.core.contracts.*; +import net.corda.core.messaging.*; +import net.corda.core.node.*; +import net.corda.core.node.services.*; +import net.corda.core.node.services.vault.*; +import net.corda.core.utilities.*; +import net.corda.flows.*; +import net.corda.nodeapi.*; +import net.corda.schemas.*; +import net.corda.smoketesting.*; +import org.bouncycastle.asn1.x500.*; +import org.junit.*; + +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import static kotlin.test.AssertionsKt.assertEquals; + +public class StandaloneCordaRPCJavaClientTest { + private List perms = Collections.singletonList("ALL"); + private Set permSet = new HashSet<>(perms); + private User rpcUser = new User("user1", "test", permSet); + + private AtomicInteger port = new AtomicInteger(15000); + + private NodeProcess notary; + private CordaRPCOps rpcProxy; + private CordaRPCConnection connection; + private NodeInfo notaryNode; + + private NodeConfig notaryConfig = new NodeConfig( + new X500Name("CN=Notary Service,O=R3,OU=corda,L=Zurich,C=CH"), + port.getAndIncrement(), + port.getAndIncrement(), + port.getAndIncrement(), + Collections.singletonList("corda.notary.validating"), + Arrays.asList(rpcUser), + null + ); + + @Before + public void setUp() { + notary = new NodeProcess.Factory().create(notaryConfig); + connection = notary.connect(); + rpcProxy = connection.getProxy(); + notaryNode = fetchNotaryIdentity(); + } + + @After + public void done() { + try { + connection.close(); + } finally { + notary.close(); + } + } + + private NodeInfo fetchNotaryIdentity() { + DataFeed, NetworkMapCache.MapChange> nodeDataFeed = rpcProxy.networkMapFeed(); + return nodeDataFeed.getSnapshot().get(0); + } + + @Test + public void testCashBalances() throws NoSuchFieldException, ExecutionException, InterruptedException { + Amount dollars123 = new Amount<>(123, Currency.getInstance("USD")); + + FlowHandle flowHandle = rpcProxy.startFlowDynamic(CashIssueFlow.class, + dollars123, OpaqueBytes.of("1".getBytes()), + notaryNode.getLegalIdentity(), notaryNode.getLegalIdentity()); + System.out.println("Started issuing cash, waiting on result"); + flowHandle.getReturnValue().get(); + + Amount balance = getBalance(Currency.getInstance("USD")); + System.out.print("Balance: " + balance + "\n"); + + assertEquals(dollars123, balance, "matching"); + } + + private Amount getBalance(Currency currency) throws NoSuchFieldException { + Field pennies = CashSchemaV1.PersistentCashState.class.getDeclaredField("pennies"); + @SuppressWarnings("unchecked") + QueryCriteria sumCriteria = new QueryCriteria.VaultCustomQueryCriteria(Builder.sum(pennies)); + + Vault.Page results = rpcProxy.vaultQueryByCriteria(sumCriteria, Cash.State.class); + if (results.getOtherResults().isEmpty()) { + return new Amount<>(0L, currency); + } else { + Assert.assertNotNull(results.getOtherResults()); + Long quantity = (Long) results.getOtherResults().get(0); + return new Amount<>(quantity, currency); + } + } +} diff --git a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt index 4656c23574..429d1fd169 100644 --- a/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt +++ b/client/rpc/src/smoke-test/kotlin/net/corda/kotlin/rpc/StandaloneCordaRPClientTest.kt @@ -6,9 +6,7 @@ import net.corda.client.rpc.CordaRPCConnection import net.corda.client.rpc.notUsed import net.corda.contracts.asset.Cash import net.corda.core.InputStreamAndHash -import net.corda.core.contracts.DOLLARS -import net.corda.core.contracts.POUNDS -import net.corda.core.contracts.SWISS_FRANCS +import net.corda.core.contracts.* import net.corda.core.crypto.SecureHash import net.corda.core.getOrThrow import net.corda.core.messaging.* @@ -21,6 +19,7 @@ import net.corda.core.utilities.loggerFor import net.corda.flows.CashIssueFlow import net.corda.flows.CashPaymentFlow import net.corda.nodeapi.User +import net.corda.schemas.CashSchemaV1 import net.corda.smoketesting.NodeConfig import net.corda.smoketesting.NodeProcess import org.apache.commons.io.output.NullOutputStream @@ -35,6 +34,7 @@ import java.util.concurrent.atomic.AtomicInteger import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotEquals +import kotlin.test.assertTrue class StandaloneCordaRPClientTest { private companion object { @@ -177,10 +177,44 @@ class StandaloneCordaRPClientTest { assertEquals(3, moreResults.totalStatesAvailable) // 629 - 100 + 100 // Check that this cash exists in the vault - val cashBalance = rpcProxy.getCashBalances() - log.info("Cash Balances: $cashBalance") - assertEquals(1, cashBalance.size) - assertEquals(629.POUNDS, cashBalance[Currency.getInstance("GBP")]) + val cashBalances = rpcProxy.getCashBalances() + log.info("Cash Balances: $cashBalances") + assertEquals(1, cashBalances.size) + assertEquals(629.POUNDS, cashBalances[Currency.getInstance("GBP")]) + } + + @Test + fun `test cash balances`() { + val startCash = rpcProxy.getCashBalances() + assertTrue(startCash.isEmpty(), "Should not start with any cash") + + val flowHandle = rpcProxy.startFlow(::CashIssueFlow, + 629.DOLLARS, OpaqueBytes.of(0), + notaryNode.legalIdentity, notaryNode.legalIdentity + ) + println("Started issuing cash, waiting on result") + flowHandle.returnValue.get() + + val balance = getBalance(USD) + println("Balance: " + balance) + assertEquals(629.DOLLARS, balance) + } + + private fun getBalance(currency: Currency): Amount { + val sum = builder { CashSchemaV1.PersistentCashState::pennies.sum() } + val sumCriteria = QueryCriteria.VaultCustomQueryCriteria(sum) + + val ccyIndex = builder { CashSchemaV1.PersistentCashState::currency.equal(currency.currencyCode) } + val ccyCriteria = QueryCriteria.VaultCustomQueryCriteria(ccyIndex) + + val results = rpcProxy.vaultQueryBy(sumCriteria.and(ccyCriteria)) + if (results.otherResults.isEmpty()) { + return Amount(0L, currency) + } else { + @Suppress("UNCHECKED_CAST") + val quantity = results.otherResults[0] as Long + return Amount(quantity, currency) + } } private fun fetchNotaryIdentity(): NodeInfo { diff --git a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt index 92817ac424..44de37183f 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt @@ -6,7 +6,7 @@ import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable import java.lang.reflect.Field import kotlin.reflect.KProperty1 -import kotlin.reflect.jvm.javaField +import kotlin.reflect.jvm.javaGetter @CordaSerializable enum class BinaryLogicalOperator { @@ -66,9 +66,9 @@ sealed class CriteriaExpression { } @CordaSerializable -sealed class Column { - data class Java(val field: Field) : Column() - data class Kotlin(val property: KProperty1) : Column() +class Column(val name: String, val declaringClass: Class<*>) { + constructor(field: Field) : this(field.name, field.declaringClass) + constructor(property: KProperty1) : this(property.name, property.javaGetter!!.declaringClass) } @CordaSerializable @@ -92,19 +92,8 @@ fun resolveEnclosingObjectFromExpression(expression: CriteriaExpression resolveEnclosingObjectFromColumn(column: Column): Class { - return when (column) { - is Column.Java -> column.field.declaringClass as Class - is Column.Kotlin -> column.property.javaField!!.declaringClass as Class - } -} - -fun getColumnName(column: Column): String { - return when (column) { - is Column.Java -> column.field.name - is Column.Kotlin -> column.property.name - } -} +fun resolveEnclosingObjectFromColumn(column: Column): Class = column.declaringClass as Class +fun getColumnName(column: Column): String = column.name /** * Pagination and Ordering @@ -210,14 +199,14 @@ sealed class SortAttribute { object Builder { fun > compare(operator: BinaryComparisonOperator, value: R) = ColumnPredicate.BinaryComparison(operator, value) - fun KProperty1.predicate(predicate: ColumnPredicate) = CriteriaExpression.ColumnPredicateExpression(Column.Kotlin(this), predicate) + fun KProperty1.predicate(predicate: ColumnPredicate) = CriteriaExpression.ColumnPredicateExpression(Column(this), predicate) - fun Field.predicate(predicate: ColumnPredicate) = CriteriaExpression.ColumnPredicateExpression(Column.Java(this), predicate) + fun Field.predicate(predicate: ColumnPredicate) = CriteriaExpression.ColumnPredicateExpression(Column(this), predicate) - fun KProperty1.functionPredicate(predicate: ColumnPredicate, groupByColumns: List>? = null, orderBy: Sort.Direction? = null) - = CriteriaExpression.AggregateFunctionExpression(Column.Kotlin(this), predicate, groupByColumns, orderBy) - fun Field.functionPredicate(predicate: ColumnPredicate, groupByColumns: List>? = null, orderBy: Sort.Direction? = null) - = CriteriaExpression.AggregateFunctionExpression(Column.Java(this), predicate, groupByColumns, orderBy) + fun KProperty1.functionPredicate(predicate: ColumnPredicate, groupByColumns: List>? = null, orderBy: Sort.Direction? = null) + = CriteriaExpression.AggregateFunctionExpression(Column(this), predicate, groupByColumns, orderBy) + fun Field.functionPredicate(predicate: ColumnPredicate, groupByColumns: List>? = null, orderBy: Sort.Direction? = null) + = CriteriaExpression.AggregateFunctionExpression(Column(this), predicate, groupByColumns, orderBy) fun > KProperty1.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value)) fun > Field.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value)) @@ -264,34 +253,34 @@ object Builder { /** aggregate functions */ fun KProperty1.sum(groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.SUM), groupByColumns?.map { Column.Kotlin(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.SUM), groupByColumns?.map { Column(it) }, orderBy) @JvmStatic @JvmOverloads fun Field.sum(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.SUM), groupByColumns?.map { Column.Java(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.SUM), groupByColumns?.map { Column(it) }, orderBy) fun KProperty1.count() = functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.COUNT)) @JvmStatic fun Field.count() = functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.COUNT)) fun KProperty1.avg(groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.AVG), groupByColumns?.map { Column.Kotlin(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.AVG), groupByColumns?.map { Column(it) }, orderBy) @JvmStatic @JvmOverloads fun Field.avg(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.AVG), groupByColumns?.map { Column.Java(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.AVG), groupByColumns?.map { Column(it) }, orderBy) fun KProperty1.min(groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MIN), groupByColumns?.map { Column.Kotlin(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MIN), groupByColumns?.map { Column(it) }, orderBy) @JvmStatic @JvmOverloads fun Field.min(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MIN), groupByColumns?.map { Column.Java(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MIN), groupByColumns?.map { Column(it) }, orderBy) fun KProperty1.max(groupByColumns: List>? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MAX), groupByColumns?.map { Column.Kotlin(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MAX), groupByColumns?.map { Column(it) }, orderBy) @JvmStatic @JvmOverloads fun Field.max(groupByColumns: List? = null, orderBy: Sort.Direction? = null) = - functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MAX), groupByColumns?.map { Column.Java(it) }, orderBy) + functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.MAX), groupByColumns?.map { Column(it) }, orderBy) } inline fun builder(block: Builder.() -> A) = block(Builder) diff --git a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt index 36a847eec3..a2c2d00e8f 100644 --- a/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt +++ b/core/src/main/kotlin/net/corda/core/schemas/PersistentTypes.kt @@ -3,6 +3,7 @@ package net.corda.core.schemas import io.requery.Persistable import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateRef +import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.toHexString import java.io.Serializable import javax.persistence.Column @@ -49,7 +50,7 @@ open class MappedSchema(schemaFamily: Class<*>, * A super class for all mapped states exported to a schema that ensures the [StateRef] appears on the database row. The * [StateRef] will be set to the correct value by the framework (there's no need to set during mapping generation by the state itself). */ -@MappedSuperclass open class PersistentState(@EmbeddedId var stateRef: PersistentStateRef? = null) : StatePersistable +@MappedSuperclass @CordaSerializable open class PersistentState(@EmbeddedId var stateRef: PersistentStateRef? = null) : StatePersistable /** * Embedded [StateRef] representation used in state mapping. diff --git a/finance/src/main/kotlin/net/corda/schemas/CashSchemaV1.kt b/finance/src/main/kotlin/net/corda/schemas/CashSchemaV1.kt index e2ede7a16e..8e11e1f8c8 100644 --- a/finance/src/main/kotlin/net/corda/schemas/CashSchemaV1.kt +++ b/finance/src/main/kotlin/net/corda/schemas/CashSchemaV1.kt @@ -2,6 +2,7 @@ package net.corda.schemas import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState +import net.corda.core.serialization.CordaSerializable import javax.persistence.* /** @@ -13,6 +14,7 @@ object CashSchema * First version of a cash contract ORM schema that maps all fields of the [Cash] contract state as it stood * at the time of writing. */ +@CordaSerializable object CashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCashState::class.java)) { @Entity @Table(name = "contract_cash_states", diff --git a/finance/src/main/kotlin/net/corda/schemas/CommercialPaperSchemaV1.kt b/finance/src/main/kotlin/net/corda/schemas/CommercialPaperSchemaV1.kt index 2b24f6be8b..98d600ed09 100644 --- a/finance/src/main/kotlin/net/corda/schemas/CommercialPaperSchemaV1.kt +++ b/finance/src/main/kotlin/net/corda/schemas/CommercialPaperSchemaV1.kt @@ -2,6 +2,7 @@ package net.corda.schemas import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState +import net.corda.core.serialization.CordaSerializable import java.time.Instant import javax.persistence.Column import javax.persistence.Entity @@ -17,6 +18,7 @@ object CommercialPaperSchema * First version of a commercial paper contract ORM schema that maps all fields of the [CommercialPaper] contract state * as it stood at the time of writing. */ +@CordaSerializable object CommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPaperSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCommercialPaperState::class.java)) { @Entity @Table(name = "cp_states", diff --git a/node/build.gradle b/node/build.gradle index 74d9bf04e7..b389dfb409 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -41,6 +41,11 @@ sourceSets { runtimeClasspath += main.output + test.output srcDir file('src/integration-test/kotlin') } + java { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integration-test/java') + } resources { srcDir file('src/integration-test/resources') } @@ -53,6 +58,11 @@ sourceSets { runtimeClasspath += main.output srcDir file('src/smoke-test/kotlin') } + java { + compileClasspath += main.output + runtimeClasspath += main.output + srcDir file('src/smoke-test/java') + } } } diff --git a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt index 6ad1d928f5..6f383f81c2 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/HibernateQueryCriteriaParser.kt @@ -74,8 +74,8 @@ class HibernateQueryCriteriaParser(val contractType: Class, val timeCondition = criteria.timeCondition val timeInstantType = timeCondition!!.type val timeColumn = when (timeInstantType) { - QueryCriteria.TimeInstantType.RECORDED -> Column.Kotlin(VaultSchemaV1.VaultStates::recordedTime) - QueryCriteria.TimeInstantType.CONSUMED -> Column.Kotlin(VaultSchemaV1.VaultStates::consumedTime) + QueryCriteria.TimeInstantType.RECORDED -> Column(VaultSchemaV1.VaultStates::recordedTime) + QueryCriteria.TimeInstantType.CONSUMED -> Column(VaultSchemaV1.VaultStates::consumedTime) } val expression = CriteriaExpression.ColumnPredicateExpression(timeColumn, timeCondition.predicate) predicateSet.add(parseExpression(vaultStates, expression) as Predicate) @@ -93,9 +93,10 @@ class HibernateQueryCriteriaParser(val contractType: Class, } } is ColumnPredicate.BinaryComparison -> { - column as Path?> @Suppress("UNCHECKED_CAST") val literal = columnPredicate.rightLiteral as Comparable? + @Suppress("UNCHECKED_CAST") + column as Path?> when (columnPredicate.operator) { BinaryComparisonOperator.GREATER_THAN -> criteriaBuilder.greaterThan(column, literal) BinaryComparisonOperator.GREATER_THAN_OR_EQUAL -> criteriaBuilder.greaterThanOrEqualTo(column, literal) @@ -104,6 +105,7 @@ class HibernateQueryCriteriaParser(val contractType: Class, } } is ColumnPredicate.Likeness -> { + @Suppress("UNCHECKED_CAST") column as Path when (columnPredicate.operator) { LikenessOperator.LIKE -> criteriaBuilder.like(column, columnPredicate.rightLiteral) @@ -190,8 +192,8 @@ class HibernateQueryCriteriaParser(val contractType: Class, // add optional group by clauses expression.groupByColumns?.let { columns -> val groupByExpressions = - columns.map { column -> - val path = root.get(getColumnName(column)) + columns.map { _column -> + val path = root.get(getColumnName(_column)) aggregateExpressions.add(path) path } diff --git a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt index 5ef516971a..af52eb645f 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/VaultSchema.kt @@ -6,6 +6,7 @@ import net.corda.core.node.services.Vault import net.corda.core.schemas.CommonSchemaV1 import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.PersistentState +import net.corda.core.serialization.CordaSerializable import net.corda.core.utilities.OpaqueBytes import java.time.Instant import java.util.* @@ -19,6 +20,7 @@ object VaultSchema /** * First version of the Vault ORM schema */ +@CordaSerializable object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, version = 1, mappedTypes = listOf(VaultStates::class.java, VaultLinearStates::class.java, VaultFungibleStates::class.java, CommonSchemaV1.Party::class.java)) { @Entity