mirror of
https://github.com/corda/corda.git
synced 2025-02-21 09:51:57 +00:00
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.
This commit is contained in:
parent
8596f1cc17
commit
65ce5fec4b
@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<String> perms = Arrays.asList(startFlowPermission(CashPaymentFlow.class), startFlowPermission(CashIssueFlow.class));
|
||||
private Set<String> permSet = new HashSet<>(perms);
|
||||
private User rpcUser = new User("user1", "test", permSet);
|
||||
|
||||
private Node node;
|
||||
private CordaRPCClient client;
|
||||
private RPCClient.RPCConnection<CordaRPCOps> 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<ServiceInfo> services = new HashSet<>(Collections.singletonList(new ServiceInfo(ValidatingNotaryService.Companion.getType(), null)));
|
||||
ListenableFuture<Node> 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<Currency> dollars123 = new Amount<>(123, Currency.getInstance("USD"));
|
||||
|
||||
FlowHandle<AbstractCashFlow.Result> 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<Currency> balance = getBalance(Currency.getInstance("USD"));
|
||||
System.out.print("Balance: " + balance + "\n");
|
||||
|
||||
assertEquals(dollars123, balance, "matching");
|
||||
}
|
||||
|
||||
private Amount<Currency> getBalance(Currency currency) throws NoSuchFieldException {
|
||||
Field pennies = CashSchemaV1.PersistentCashState.class.getDeclaredField("pennies");
|
||||
@SuppressWarnings("unchecked")
|
||||
QueryCriteria sumCriteria = new QueryCriteria.VaultCustomQueryCriteria(Builder.sum(pennies));
|
||||
|
||||
Vault.Page<Cash.State> 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Currency> {
|
||||
val sum = builder { CashSchemaV1.PersistentCashState::pennies.sum() }
|
||||
val sumCriteria = QueryCriteria.VaultCustomQueryCriteria(sum)
|
||||
|
||||
val results = proxy.vaultQueryBy<Cash.State>(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
|
||||
|
@ -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<String> perms = Collections.singletonList("ALL");
|
||||
private Set<String> 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<List<NodeInfo>, NetworkMapCache.MapChange> nodeDataFeed = rpcProxy.networkMapFeed();
|
||||
return nodeDataFeed.getSnapshot().get(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCashBalances() throws NoSuchFieldException, ExecutionException, InterruptedException {
|
||||
Amount<Currency> dollars123 = new Amount<>(123, Currency.getInstance("USD"));
|
||||
|
||||
FlowHandle<AbstractCashFlow.Result> 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<Currency> balance = getBalance(Currency.getInstance("USD"));
|
||||
System.out.print("Balance: " + balance + "\n");
|
||||
|
||||
assertEquals(dollars123, balance, "matching");
|
||||
}
|
||||
|
||||
private Amount<Currency> getBalance(Currency currency) throws NoSuchFieldException {
|
||||
Field pennies = CashSchemaV1.PersistentCashState.class.getDeclaredField("pennies");
|
||||
@SuppressWarnings("unchecked")
|
||||
QueryCriteria sumCriteria = new QueryCriteria.VaultCustomQueryCriteria(Builder.sum(pennies));
|
||||
|
||||
Vault.Page<Cash.State> 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Currency> {
|
||||
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<Cash.State>(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 {
|
||||
|
@ -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<O, out T> {
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
sealed class Column<O, out C> {
|
||||
data class Java<O, out C>(val field: Field) : Column<O, C>()
|
||||
data class Kotlin<O, out C>(val property: KProperty1<O, C?>) : Column<O, C>()
|
||||
class Column<O, out C>(val name: String, val declaringClass: Class<*>) {
|
||||
constructor(field: Field) : this(field.name, field.declaringClass)
|
||||
constructor(property: KProperty1<O, C?>) : this(property.name, property.javaGetter!!.declaringClass)
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
@ -92,19 +92,8 @@ fun <O, R> resolveEnclosingObjectFromExpression(expression: CriteriaExpression<O
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <O, C> resolveEnclosingObjectFromColumn(column: Column<O, C>): Class<O> {
|
||||
return when (column) {
|
||||
is Column.Java -> column.field.declaringClass as Class<O>
|
||||
is Column.Kotlin -> column.property.javaField!!.declaringClass as Class<O>
|
||||
}
|
||||
}
|
||||
|
||||
fun <O, C> getColumnName(column: Column<O, C>): String {
|
||||
return when (column) {
|
||||
is Column.Java -> column.field.name
|
||||
is Column.Kotlin -> column.property.name
|
||||
}
|
||||
}
|
||||
fun <O, C> resolveEnclosingObjectFromColumn(column: Column<O, C>): Class<O> = column.declaringClass as Class<O>
|
||||
fun <O, C> getColumnName(column: Column<O, C>): String = column.name
|
||||
|
||||
/**
|
||||
* Pagination and Ordering
|
||||
@ -210,14 +199,14 @@ sealed class SortAttribute {
|
||||
object Builder {
|
||||
|
||||
fun <R : Comparable<R>> compare(operator: BinaryComparisonOperator, value: R) = ColumnPredicate.BinaryComparison(operator, value)
|
||||
fun <O, R> KProperty1<O, R?>.predicate(predicate: ColumnPredicate<R>) = CriteriaExpression.ColumnPredicateExpression(Column.Kotlin(this), predicate)
|
||||
fun <O, R> KProperty1<O, R?>.predicate(predicate: ColumnPredicate<R>) = CriteriaExpression.ColumnPredicateExpression(Column(this), predicate)
|
||||
|
||||
fun <R> Field.predicate(predicate: ColumnPredicate<R>) = CriteriaExpression.ColumnPredicateExpression(Column.Java<Any, R>(this), predicate)
|
||||
fun <R> Field.predicate(predicate: ColumnPredicate<R>) = CriteriaExpression.ColumnPredicateExpression(Column<Any, R>(this), predicate)
|
||||
|
||||
fun <O, R> KProperty1<O, R?>.functionPredicate(predicate: ColumnPredicate<R>, groupByColumns: List<Column.Kotlin<O, R>>? = null, orderBy: Sort.Direction? = null)
|
||||
= CriteriaExpression.AggregateFunctionExpression(Column.Kotlin(this), predicate, groupByColumns, orderBy)
|
||||
fun <R> Field.functionPredicate(predicate: ColumnPredicate<R>, groupByColumns: List<Column.Java<Any, R>>? = null, orderBy: Sort.Direction? = null)
|
||||
= CriteriaExpression.AggregateFunctionExpression(Column.Java<Any, R>(this), predicate, groupByColumns, orderBy)
|
||||
fun <O, R> KProperty1<O, R?>.functionPredicate(predicate: ColumnPredicate<R>, groupByColumns: List<Column<O, R>>? = null, orderBy: Sort.Direction? = null)
|
||||
= CriteriaExpression.AggregateFunctionExpression(Column(this), predicate, groupByColumns, orderBy)
|
||||
fun <R> Field.functionPredicate(predicate: ColumnPredicate<R>, groupByColumns: List<Column<Any, R>>? = null, orderBy: Sort.Direction? = null)
|
||||
= CriteriaExpression.AggregateFunctionExpression(Column<Any, R>(this), predicate, groupByColumns, orderBy)
|
||||
|
||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value))
|
||||
fun <R : Comparable<R>> Field.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value))
|
||||
@ -264,34 +253,34 @@ object Builder {
|
||||
|
||||
/** aggregate functions */
|
||||
fun <O, R> KProperty1<O, R?>.sum(groupByColumns: List<KProperty1<O, R>>? = 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 <R> Field.sum(groupByColumns: List<Field>? = null, orderBy: Sort.Direction? = null) =
|
||||
functionPredicate(ColumnPredicate.AggregateFunction<R>(AggregateFunctionType.SUM), groupByColumns?.map { Column.Java<Any,R>(it) }, orderBy)
|
||||
functionPredicate(ColumnPredicate.AggregateFunction<R>(AggregateFunctionType.SUM), groupByColumns?.map { Column<Any,R>(it) }, orderBy)
|
||||
|
||||
fun <O, R> KProperty1<O, R?>.count() = functionPredicate(ColumnPredicate.AggregateFunction(AggregateFunctionType.COUNT))
|
||||
@JvmStatic fun Field.count() = functionPredicate(ColumnPredicate.AggregateFunction<Any>(AggregateFunctionType.COUNT))
|
||||
|
||||
fun <O, R> KProperty1<O, R?>.avg(groupByColumns: List<KProperty1<O, R>>? = 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 <R> Field.avg(groupByColumns: List<Field>? = null, orderBy: Sort.Direction? = null) =
|
||||
functionPredicate(ColumnPredicate.AggregateFunction<R>(AggregateFunctionType.AVG), groupByColumns?.map { Column.Java<Any,R>(it) }, orderBy)
|
||||
functionPredicate(ColumnPredicate.AggregateFunction<R>(AggregateFunctionType.AVG), groupByColumns?.map { Column<Any,R>(it) }, orderBy)
|
||||
|
||||
fun <O, R> KProperty1<O, R?>.min(groupByColumns: List<KProperty1<O, R>>? = 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 <R> Field.min(groupByColumns: List<Field>? = null, orderBy: Sort.Direction? = null) =
|
||||
functionPredicate(ColumnPredicate.AggregateFunction<R>(AggregateFunctionType.MIN), groupByColumns?.map { Column.Java<Any,R>(it) }, orderBy)
|
||||
functionPredicate(ColumnPredicate.AggregateFunction<R>(AggregateFunctionType.MIN), groupByColumns?.map { Column<Any,R>(it) }, orderBy)
|
||||
|
||||
fun <O, R> KProperty1<O, R?>.max(groupByColumns: List<KProperty1<O, R>>? = 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 <R> Field.max(groupByColumns: List<Field>? = null, orderBy: Sort.Direction? = null) =
|
||||
functionPredicate(ColumnPredicate.AggregateFunction<R>(AggregateFunctionType.MAX), groupByColumns?.map { Column.Java<Any,R>(it) }, orderBy)
|
||||
functionPredicate(ColumnPredicate.AggregateFunction<R>(AggregateFunctionType.MAX), groupByColumns?.map { Column<Any,R>(it) }, orderBy)
|
||||
}
|
||||
|
||||
inline fun <A> builder(block: Builder.() -> A) = block(Builder)
|
||||
|
@ -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.
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,8 +74,8 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
|
||||
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<out ContractState>,
|
||||
}
|
||||
}
|
||||
is ColumnPredicate.BinaryComparison -> {
|
||||
column as Path<Comparable<Any?>?>
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val literal = columnPredicate.rightLiteral as Comparable<Any?>?
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
column as Path<Comparable<Any?>?>
|
||||
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<out ContractState>,
|
||||
}
|
||||
}
|
||||
is ColumnPredicate.Likeness -> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
column as Path<String?>
|
||||
when (columnPredicate.operator) {
|
||||
LikenessOperator.LIKE -> criteriaBuilder.like(column, columnPredicate.rightLiteral)
|
||||
@ -190,8 +192,8 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
|
||||
// add optional group by clauses
|
||||
expression.groupByColumns?.let { columns ->
|
||||
val groupByExpressions =
|
||||
columns.map { column ->
|
||||
val path = root.get<Any?>(getColumnName(column))
|
||||
columns.map { _column ->
|
||||
val path = root.get<Any?>(getColumnName(_column))
|
||||
aggregateExpressions.add(path)
|
||||
path
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user