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 fa98e1d54b..faac1f7fef 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 @@ -5,6 +5,10 @@ package net.corda.core.node.services.vault import net.corda.core.DoNotImplement import net.corda.core.internal.declaredField import net.corda.core.internal.uncheckedCast +import net.corda.core.node.services.vault.CollectionOperator.* +import net.corda.core.node.services.vault.ColumnPredicate.* +import net.corda.core.node.services.vault.EqualityComparisonOperator.* +import net.corda.core.node.services.vault.LikenessOperator.* import net.corda.core.schemas.PersistentState import net.corda.core.serialization.CordaSerializable import java.lang.reflect.Field @@ -24,7 +28,9 @@ enum class BinaryLogicalOperator : Operator { enum class EqualityComparisonOperator : Operator { EQUAL, - NOT_EQUAL + NOT_EQUAL, + EQUAL_IGNORE_CASE, + NOT_EQUAL_IGNORE_CASE } enum class BinaryComparisonOperator : Operator { @@ -41,12 +47,16 @@ enum class NullOperator : Operator { enum class LikenessOperator : Operator { LIKE, - NOT_LIKE + NOT_LIKE, + LIKE_IGNORE_CASE, + NOT_LIKE_IGNORE_CASE } enum class CollectionOperator : Operator { IN, - NOT_IN + NOT_IN, + IN_IGNORE_CASE, + NOT_IN_IGNORE_CASE } @CordaSerializable @@ -251,27 +261,45 @@ object Builder { fun > Field.comparePredicate(operator: BinaryComparisonOperator, value: R) = info().comparePredicate(operator, value) fun > FieldInfo.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value)) - fun KProperty1.equal(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value)) - fun KProperty1.notEqual(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value)) + @JvmOverloads + fun KProperty1.equal(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch)) + + @JvmOverloads + fun KProperty1.notEqual(value: R, exactMatch: Boolean = true) = predicate(Builder.notEqual(value, exactMatch)) + fun > KProperty1.lessThan(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN, value) + fun > KProperty1.lessThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN_OR_EQUAL, value) + fun > KProperty1.greaterThan(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN, value) + fun > KProperty1.greaterThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL, value) + fun > KProperty1.between(from: R, to: R) = predicate(ColumnPredicate.Between(from, to)) - fun > KProperty1.`in`(collection: Collection) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection)) - fun > KProperty1.notIn(collection: Collection) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection)) + + @JvmOverloads + fun > KProperty1.`in`(collection: Collection, exactMatch: Boolean = true) = predicate(Builder.`in`(collection, exactMatch)) + + @JvmOverloads + fun > KProperty1.notIn(collection: Collection, exactMatch: Boolean = true) = predicate(Builder.notIn(collection, exactMatch)) @JvmStatic + @JvmOverloads @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") - fun Field.equal(value: R) = info().equal(value) - @JvmStatic - fun FieldInfo.equal(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value)) + fun Field.equal(value: R, exactMatch: Boolean = true) = info().equal(value, exactMatch) @JvmStatic - @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") - fun Field.notEqual(value: R) = info().notEqual(value) + @JvmOverloads + fun FieldInfo.equal(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch)) + @JvmStatic - fun FieldInfo.notEqual(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value)) + @JvmOverloads + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun Field.notEqual(value: R, exactMatch: Boolean = true) = info().notEqual(value, exactMatch) + + @JvmStatic + @JvmOverloads + fun FieldInfo.notEqual(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch)) @JvmStatic @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") @@ -304,44 +332,77 @@ object Builder { fun > FieldInfo.between(from: R, to: R) = predicate(ColumnPredicate.Between(from, to)) @JvmStatic + @JvmOverloads @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") - fun > Field.`in`(collection: Collection) = info().`in`(collection) - fun > FieldInfo.`in`(collection: Collection) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection)) + fun > Field.`in`(collection: Collection, exactMatch: Boolean = true) = info().`in`(collection, exactMatch) @JvmStatic - @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") - fun > Field.notIn(collection: Collection) = info().notIn(collection) - @JvmStatic - fun > FieldInfo.notIn(collection: Collection) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection)) + @JvmOverloads + fun > FieldInfo.`in`(collection: Collection, exactMatch: Boolean = true) = predicate(Builder.`in`(collection, exactMatch)) + + @JvmStatic + @JvmOverloads + @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") + fun > Field.notIn(collection: Collection, exactMatch: Boolean = true) = info().notIn(collection, exactMatch) + + @JvmStatic + @JvmOverloads + fun > FieldInfo.notIn(collection: Collection, exactMatch: Boolean = true) = predicate(Builder.notIn(collection, exactMatch)) + + @JvmOverloads + fun equal(value: R, exactMatch: Boolean = true) = EqualityComparison(if (exactMatch) EQUAL else EQUAL_IGNORE_CASE, value) + + @JvmOverloads + fun notEqual(value: R, exactMatch: Boolean = true) = EqualityComparison(if (exactMatch) NOT_EQUAL else NOT_EQUAL_IGNORE_CASE, value) - fun equal(value: R) = ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value) - fun notEqual(value: R) = ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value) fun > lessThan(value: R) = compare(BinaryComparisonOperator.LESS_THAN, value) + fun > lessThanOrEqual(value: R) = compare(BinaryComparisonOperator.LESS_THAN_OR_EQUAL, value) + fun > greaterThan(value: R) = compare(BinaryComparisonOperator.GREATER_THAN, value) + fun > greaterThanOrEqual(value: R) = compare(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL, value) + fun > between(from: R, to: R) = ColumnPredicate.Between(from, to) - fun > `in`(collection: Collection) = ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection) - fun > notIn(collection: Collection) = ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection) - fun like(string: String) = ColumnPredicate.Likeness(LikenessOperator.LIKE, string) - fun notLike(string: String) = ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string) + + @JvmOverloads + fun > `in`(collection: Collection, exactMatch: Boolean = true) = CollectionExpression(if (exactMatch) IN else IN_IGNORE_CASE, collection) + + @JvmOverloads + fun > notIn(collection: Collection, exactMatch: Boolean = true) = CollectionExpression(if (exactMatch) NOT_IN else NOT_IN_IGNORE_CASE, collection) + + @JvmOverloads + fun like(string: String, exactMatch: Boolean = true) = Likeness(if (exactMatch) LIKE else LIKE_IGNORE_CASE, string) + + @JvmOverloads + fun notLike(string: String, exactMatch: Boolean = true) = Likeness(if (exactMatch) NOT_LIKE else NOT_LIKE_IGNORE_CASE, string) + fun isNull() = ColumnPredicate.NullExpression(NullOperator.IS_NULL) fun isNotNull() = ColumnPredicate.NullExpression(NullOperator.NOT_NULL) + @JvmOverloads + fun KProperty1.like(string: String, exactMatch: Boolean = true) = predicate(Builder.like(string, exactMatch)) - fun KProperty1.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string)) @JvmStatic + @JvmOverloads @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") - fun Field.like(string: String) = info().like(string) - @JvmStatic - fun FieldInfo.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string)) + fun Field.like(string: String, exactMatch: Boolean = true) = info().like(string, exactMatch) - fun KProperty1.notLike(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string)) @JvmStatic + @JvmOverloads + fun FieldInfo.like(string: String, exactMatch: Boolean = true) = predicate(Builder.like(string, exactMatch)) + + @JvmOverloads + fun KProperty1.notLike(string: String, exactMatch: Boolean = true) = predicate(Builder.notLike(string, exactMatch)) + + @JvmStatic + @JvmOverloads @Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.") - fun Field.notLike(string: String) = info().notLike(string) + fun Field.notLike(string: String, exactMatch: Boolean = true) = info().notLike(string, exactMatch) + @JvmStatic - fun FieldInfo.notLike(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string)) + @JvmOverloads + fun FieldInfo.notLike(string: String, exactMatch: Boolean = true) = predicate(Builder.notLike(string, exactMatch)) fun KProperty1.isNull() = predicate(ColumnPredicate.NullExpression(NullOperator.IS_NULL)) @JvmStatic diff --git a/docs/source/api-vault-query.rst b/docs/source/api-vault-query.rst index 1b80d50dcf..a012b84ea7 100644 --- a/docs/source/api-vault-query.rst +++ b/docs/source/api-vault-query.rst @@ -166,6 +166,44 @@ An example of a custom query in Java is illustrated here: where an anonymous party does not resolve to an X500 name via the ``IdentityService``, no query results will ever be produced. For performance reasons, queries do not use ``PublicKey`` as search criteria. +Custom queries can be either case sensitive or case insensitive. They are defined via a ``Boolean`` as one of the function parameters of each operator function. By default each operator is case sensitive. + +An example of a case sensitive custom query operator is illustrated here: + +.. container:: codeset + + .. sourcecode:: kotlin + + val currencyIndex = PersistentCashState::currency.equal(USD.currencyCode, true) + +.. note:: The ``Boolean`` input of ``true`` in this example could be removed since the function will default to ``true`` when not provided. + +An example of a case insensitive custom query operator is illustrated here: + +.. container:: codeset + + .. sourcecode:: kotlin + + val currencyIndex = PersistentCashState::currency.equal(USD.currencyCode, false) + +An example of a case sensitive custom query operator in Java is illustrated here: + +.. container:: codeset + + .. sourcecode:: java + + FieldInfo attributeCurrency = getField("currency", CashSchemaV1.PersistentCashState.class); + CriteriaExpression currencyIndex = Builder.equal(attributeCurrency, "USD", true); + +An example of a case insensitive custom query operator in Java is illustrated here: + +.. container:: codeset + + .. sourcecode:: java + + FieldInfo attributeCurrency = getField("currency", CashSchemaV1.PersistentCashState.class); + CriteriaExpression currencyIndex = Builder.equal(attributeCurrency, "USD", false); + Pagination ---------- The API provides support for paging where large numbers of results are expected (by default, a page size is set to 200 diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index ce151efb88..884e9b60d0 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -6,6 +6,9 @@ release, see :doc:`upgrade-notes`. Unreleased ---------- + +* Case insensitive vault queries can be specified via a boolean on applicable SQL criteria builder operators. By default queries will be case sensitive. + * Getter added to ``CordaRPCOps`` for the node's network parameters. * The RPC client library now checks at startup whether the server is of the client libraries major version or higher. diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/process/Command.kt b/experimental/behave/src/main/kotlin/net/corda/behave/process/Command.kt index 8599bbd3cc..6f55e5a871 100644 --- a/experimental/behave/src/main/kotlin/net/corda/behave/process/Command.kt +++ b/experimental/behave/src/main/kotlin/net/corda/behave/process/Command.kt @@ -4,7 +4,7 @@ import net.corda.behave.await import net.corda.behave.file.currentDirectory import net.corda.behave.process.output.OutputListener import net.corda.behave.waitFor -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.core.utilities.minutes import net.corda.core.utilities.seconds import rx.Observable @@ -20,8 +20,10 @@ open class Command( private val directory: Path = currentDirectory, private val timeout: Duration = 2.minutes ): Closeable { - - protected val log = loggerFor() + companion object { + private val WAIT_BEFORE_KILL: Duration = 5.seconds + private val log = contextLogger() + } private val terminationLatch = CountDownLatch(1) @@ -36,17 +38,12 @@ open class Command( var exitCode = -1 private set - val output: Observable = Observable.create({ emitter -> + val output: Observable = Observable.create { emitter -> outputListener = object : OutputListener { - override fun onNewLine(line: String) { - emitter.onNext(line) - } - - override fun onEndOfStream() { - emitter.onCompleted() - } + override fun onNewLine(line: String) = emitter.onNext(line) + override fun onEndOfStream() = emitter.onCompleted() } - }).share() + }.share() private val thread = Thread(Runnable { try { @@ -132,12 +129,13 @@ open class Command( } override fun close() { + if (process?.isAlive == true) { + kill() + } waitFor() } - fun run() = use { _ -> } - - fun use(action: (Command) -> Unit): Int { + fun run(action: (Command) -> Unit = { }): Int { try { start() action(this) @@ -147,8 +145,8 @@ open class Command( return exitCode } - fun use(subscriber: Subscriber, action: (Command, Observable) -> Unit = { _, _ -> }): Int { - use { + fun run(subscriber: Subscriber, action: (Command, Observable) -> Unit = { _, _ -> }): Int { + run { output.subscribe(subscriber) start() action(this, output) @@ -157,11 +155,4 @@ open class Command( } override fun toString() = "Command(${command.joinToString(" ")})" - - companion object { - - private val WAIT_BEFORE_KILL: Duration = 5.seconds - - } - } diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/service/ContainerService.kt b/experimental/behave/src/main/kotlin/net/corda/behave/service/ContainerService.kt index da9d31ef20..70763d06f1 100644 --- a/experimental/behave/src/main/kotlin/net/corda/behave/service/ContainerService.kt +++ b/experimental/behave/src/main/kotlin/net/corda/behave/service/ContainerService.kt @@ -78,9 +78,7 @@ abstract class ContainerService( override fun checkPrerequisites() { if (!client.listImages().any { true == it.repoTags()?.contains(imageReference) }) { log.info("Pulling image $imageReference ...") - client.pull(imageReference, { _ -> - run { } - }) + client.pull(imageReference) { } log.info("Image $imageReference downloaded") } } diff --git a/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt b/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt index 747c82cdae..88152420dd 100644 --- a/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt +++ b/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt @@ -20,7 +20,7 @@ class CommandTests { @Test fun `output stream for command can be observed`() { val subscriber = TestSubscriber() - val exitCode = Command(listOf("ls", "/")).use(subscriber) { _, _ -> + val exitCode = Command(listOf("ls", "/")).run(subscriber) { _, _ -> subscriber.awaitTerminalEvent() subscriber.assertCompleted() subscriber.assertNoErrors() diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt index ab840ed387..456db77e9b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/DevIdentityGenerator.kt @@ -12,6 +12,8 @@ import net.corda.nodeapi.internal.config.SslConfiguration import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_ALIAS_PREFIX +import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_ALIAS_PREFIX import org.slf4j.LoggerFactory import java.nio.file.Path import java.security.KeyPair @@ -25,11 +27,6 @@ import java.security.PublicKey object DevIdentityGenerator { private val log = LoggerFactory.getLogger(javaClass) - // TODO These don't need to be prefixes but can be the full aliases - // TODO Move these constants out of here as the node needs access to them - const val NODE_IDENTITY_ALIAS_PREFIX = "identity" - const val DISTRIBUTED_NOTARY_ALIAS_PREFIX = "distributed-notary" - /** Install a node key store for the given node directory using the given legal name. */ fun installKeyStoreWithNodeIdentity(nodeDir: Path, legalName: CordaX500Name): Party { val certificatesDirectory = nodeDir / "certificates" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index d2efc05c85..23fcce28dd 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -48,6 +48,10 @@ object X509Utilities { const val CORDA_CLIENT_TLS = "cordaclienttls" const val CORDA_CLIENT_CA = "cordaclientca" + // TODO These don't need to be prefixes, but can be the full aliases. + const val NODE_IDENTITY_ALIAS_PREFIX = "identity" + const val DISTRIBUTED_NOTARY_ALIAS_PREFIX = "distributed-notary" + val DEFAULT_VALIDITY_WINDOW = Pair(0.millis, 3650.days) /** diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 0826c27b6a..f10930dd75 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -74,6 +74,8 @@ import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_ALIAS_PREFIX +import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_ALIAS_PREFIX import net.corda.nodeapi.internal.persistence.* import net.corda.nodeapi.internal.storeLegalIdentity import net.corda.tools.shell.InteractiveShell @@ -211,7 +213,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, /** * Completes once the node has successfully registered with the network map service - * or has loaded network map data from local database + * or has loaded network map data from local database. */ val nodeReadyFuture: CordaFuture get() = networkMapCache.nodeReady.map { Unit } @@ -374,7 +376,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, } } - /** Subclasses must override this to create a [StartedNode] of the desired type, using the provided machinery. */ + /** Subclasses must override this to create a "started" node of the desired type, using the provided machinery. */ abstract fun createStartedNode(nodeInfo: NodeInfo, rpcOps: CordaRPCOps, notaryService: NotaryService?): S private fun verifyCheckpointsCompatible(tokenizableServices: List) { @@ -546,7 +548,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, private fun isNotaryService(serviceClass: Class<*>) = NotaryService::class.java.isAssignableFrom(serviceClass) /** - * This customizes the ServiceHub for each CordaService that is initiating flows + * This customizes the ServiceHub for each CordaService that is initiating flows. */ // TODO Move this into its own file private class AppServiceHubImpl(private val serviceHub: ServiceHub, private val flowStarter: FlowStarter) : AppServiceHub, ServiceHub by serviceHub { @@ -827,7 +829,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration, // Meanwhile, we let the remote service send us updates until the acknowledgment buffer overflows and it // unsubscribes us forcibly, rather than blocking the shutdown process. - // Run shutdown hooks in opposite order to starting + // Run shutdown hooks in opposite order to starting. for (toRun in runOnStop.reversed()) { toRun() } @@ -847,11 +849,11 @@ abstract class AbstractNode(val configuration: NodeConfiguration, val keyStore = configuration.signingCertificateStore.get() val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) { - // Node's main identity or if it's a single node notary - Pair(DevIdentityGenerator.NODE_IDENTITY_ALIAS_PREFIX, configuration.myLegalName) + // Node's main identity or if it's a single node notary. + Pair(NODE_IDENTITY_ALIAS_PREFIX, configuration.myLegalName) } else { // The node is part of a distributed notary whose identity must already be generated beforehand. - Pair(DevIdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX, null) + Pair(DISTRIBUTED_NOTARY_ALIAS_PREFIX, null) } // TODO: Integrate with Key management service? val privateKeyAlias = "$id-private-key" 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 76c4e2b09d..ec612fdeec 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 @@ -7,6 +7,13 @@ import net.corda.core.internal.uncheckedCast import net.corda.core.node.services.Vault import net.corda.core.node.services.VaultQueryException import net.corda.core.node.services.vault.* +import net.corda.core.node.services.vault.BinaryComparisonOperator.* +import net.corda.core.node.services.vault.CollectionOperator.* +import net.corda.core.node.services.vault.ColumnPredicate.* +import net.corda.core.node.services.vault.EqualityComparisonOperator.* +import net.corda.core.node.services.vault.LikenessOperator.* +import net.corda.core.node.services.vault.NullOperator.IS_NULL +import net.corda.core.node.services.vault.NullOperator.NOT_NULL import net.corda.core.node.services.vault.QueryCriteria.CommonQueryCriteria import net.corda.core.schemas.PersistentState import net.corda.core.schemas.PersistentStateRef @@ -54,54 +61,93 @@ abstract class AbstractQueryCriteriaParser, in P: protected fun columnPredicateToPredicate(column: Path, columnPredicate: ColumnPredicate<*>): Predicate { return when (columnPredicate) { - is ColumnPredicate.EqualityComparison -> { - val literal = columnPredicate.rightLiteral - when (columnPredicate.operator) { - EqualityComparisonOperator.EQUAL -> criteriaBuilder.equal(column, literal) - EqualityComparisonOperator.NOT_EQUAL -> criteriaBuilder.notEqual(column, literal) - } - } - is ColumnPredicate.BinaryComparison -> { - val literal: Comparable? = uncheckedCast(columnPredicate.rightLiteral) - @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) - BinaryComparisonOperator.LESS_THAN -> criteriaBuilder.lessThan(column, literal) - BinaryComparisonOperator.LESS_THAN_OR_EQUAL -> criteriaBuilder.lessThanOrEqualTo(column, literal) - } - } - is ColumnPredicate.Likeness -> { - @Suppress("UNCHECKED_CAST") - column as Path - when (columnPredicate.operator) { - LikenessOperator.LIKE -> criteriaBuilder.like(column, columnPredicate.rightLiteral) - LikenessOperator.NOT_LIKE -> criteriaBuilder.notLike(column, columnPredicate.rightLiteral) - } - } - is ColumnPredicate.CollectionExpression -> { - when (columnPredicate.operator) { - CollectionOperator.IN -> column.`in`(columnPredicate.rightLiteral) - CollectionOperator.NOT_IN -> criteriaBuilder.not(column.`in`(columnPredicate.rightLiteral)) - } - } - is ColumnPredicate.Between -> { - @Suppress("UNCHECKED_CAST") - column as Path?> - val fromLiteral: Comparable? = uncheckedCast(columnPredicate.rightFromLiteral) - val toLiteral: Comparable? = uncheckedCast(columnPredicate.rightToLiteral) - criteriaBuilder.between(column, fromLiteral, toLiteral) - } - is ColumnPredicate.NullExpression -> { - when (columnPredicate.operator) { - NullOperator.IS_NULL -> criteriaBuilder.isNull(column) - NullOperator.NOT_NULL -> criteriaBuilder.isNotNull(column) - } - } + is EqualityComparison -> equalityComparisonToPredicate(column, columnPredicate) + is BinaryComparison -> binaryComparisonToPredicate(column, columnPredicate) + is Likeness -> likeComparisonToPredicate(column, columnPredicate) + is CollectionExpression -> collectionComparisonToPredicate(column, columnPredicate) + is Between -> betweenComparisonToPredicate(column, columnPredicate) + is NullExpression -> nullComparisonToPredicate(column, columnPredicate) else -> throw VaultQueryException("Not expecting $columnPredicate") } } + + private fun equalityComparisonToPredicate(column: Path, columnPredicate: EqualityComparison<*>): Predicate { + val literal = columnPredicate.rightLiteral + return if (literal is String) { + @Suppress("UNCHECKED_CAST") + column as Path + when (columnPredicate.operator) { + EQUAL -> criteriaBuilder.equal(column, literal) + EQUAL_IGNORE_CASE -> criteriaBuilder.equal(criteriaBuilder.upper(column), literal.toUpperCase()) + NOT_EQUAL -> criteriaBuilder.notEqual(column, literal) + NOT_EQUAL_IGNORE_CASE -> criteriaBuilder.notEqual(criteriaBuilder.upper(column), literal.toUpperCase()) + } + } else { + when (columnPredicate.operator) { + EQUAL, EQUAL_IGNORE_CASE -> criteriaBuilder.equal(column, literal) + NOT_EQUAL, NOT_EQUAL_IGNORE_CASE -> criteriaBuilder.notEqual(column, literal) + } + } + } + + private fun binaryComparisonToPredicate(column: Path, columnPredicate: BinaryComparison<*>): Predicate { + val literal: Comparable? = uncheckedCast(columnPredicate.rightLiteral) + @Suppress("UNCHECKED_CAST") + column as Path?> + return when (columnPredicate.operator) { + GREATER_THAN -> criteriaBuilder.greaterThan(column, literal) + GREATER_THAN_OR_EQUAL -> criteriaBuilder.greaterThanOrEqualTo(column, literal) + LESS_THAN -> criteriaBuilder.lessThan(column, literal) + LESS_THAN_OR_EQUAL -> criteriaBuilder.lessThanOrEqualTo(column, literal) + } + } + + private fun likeComparisonToPredicate(column: Path, columnPredicate: Likeness): Predicate { + @Suppress("UNCHECKED_CAST") + column as Path + return when (columnPredicate.operator) { + LIKE -> criteriaBuilder.like(column, columnPredicate.rightLiteral) + LIKE_IGNORE_CASE -> criteriaBuilder.like(criteriaBuilder.upper(column), columnPredicate.rightLiteral.toUpperCase()) + NOT_LIKE -> criteriaBuilder.notLike(column, columnPredicate.rightLiteral) + NOT_LIKE_IGNORE_CASE -> criteriaBuilder.notLike(criteriaBuilder.upper(column), columnPredicate.rightLiteral.toUpperCase()) + } + } + + private fun collectionComparisonToPredicate(column: Path, columnPredicate: CollectionExpression<*>): Predicate { + val literal = columnPredicate.rightLiteral + return if (literal.any { it is String }) { + @Suppress("UNCHECKED_CAST") + column as Path + @Suppress("UNCHECKED_CAST") + literal as Collection + when (columnPredicate.operator) { + IN -> column.`in`(literal) + IN_IGNORE_CASE -> criteriaBuilder.upper(column).`in`(literal.map { it.toUpperCase() }) + NOT_IN -> criteriaBuilder.not(column.`in`(literal)) + NOT_IN_IGNORE_CASE -> criteriaBuilder.not(criteriaBuilder.upper(column).`in`(literal.map { it.toUpperCase() })) + } + } else { + when (columnPredicate.operator) { + IN, IN_IGNORE_CASE -> column.`in`(literal) + NOT_IN, NOT_IN_IGNORE_CASE -> criteriaBuilder.not(column.`in`(literal)) + } + } + } + + private fun betweenComparisonToPredicate(column: Path, columnPredicate: Between<*>): Predicate { + @Suppress("UNCHECKED_CAST") + column as Path?> + val fromLiteral: Comparable? = uncheckedCast(columnPredicate.rightFromLiteral) + val toLiteral: Comparable? = uncheckedCast(columnPredicate.rightToLiteral) + return criteriaBuilder.between(column, fromLiteral, toLiteral) + } + + private fun nullComparisonToPredicate(column: Path, columnPredicate: NullExpression<*>): Predicate { + return when (columnPredicate.operator) { + IS_NULL -> criteriaBuilder.isNull(column) + NOT_NULL -> criteriaBuilder.isNotNull(column) + } + } } class HibernateAttachmentQueryCriteriaParser(override val criteriaBuilder: CriteriaBuilder, @@ -474,7 +520,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class).values.map { (it as LiteralExpression).literal }.toSet() if (existingTypes != contractStateTypes) { diff --git a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt index 2404f319f4..a98183f6a6 100644 --- a/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt +++ b/node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt @@ -800,6 +800,139 @@ abstract class VaultQueryTestsBase : VaultQueryParties { } } + @Test + fun `logical operator case insensitive EQUAL`() { + database.transaction { + listOf(USD, GBP, CHF).forEach { + vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER) + } + val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.equal("gBp", false) } + val criteria = VaultCustomQueryCriteria(logicalExpression) + val results = vaultService.queryBy(criteria) + assertThat(results.states).hasSize(1) + } + } + + @Test + fun `logical operator case insensitive EQUAL does not affect numbers`() { + database.transaction { + listOf(USD, GBP, CHF).forEach { + vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER) + } + val logicalExpression = builder { CashSchemaV1.PersistentCashState::pennies.equal(10000, false) } + val criteria = VaultCustomQueryCriteria(logicalExpression) + val results = vaultService.queryBy(criteria) + assertThat(results.states).hasSize(3) + } + } + + @Test + fun `logical operator case insensitive NOT_EQUAL does not return results containing the same characters as the case insensitive string`() { + database.transaction { + listOf(USD, GBP, CHF).forEach { + vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER) + } + val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.notEqual("gBp", false) } + val criteria = VaultCustomQueryCriteria(logicalExpression) + val results = vaultService.queryBy(criteria) + assertThat(results.states).hasSize(2) + } + } + + @Test + fun `logical operator case insensitive NOT_EQUAL does not affect numbers`() { + database.transaction { + listOf(USD, CHF).forEach { + vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER) + } + vaultFiller.fillWithSomeTestCash(AMOUNT(50, GBP), notaryServices, 1, DUMMY_CASH_ISSUER) + val logicalExpression = builder { CashSchemaV1.PersistentCashState::pennies.notEqual(10000, false) } + val criteria = VaultCustomQueryCriteria(logicalExpression) + val results = vaultService.queryBy(criteria) + assertThat(results.states).hasSize(1) + } + } + + @Test + fun `logical operator case insensitive IN`() { + database.transaction { + listOf(USD, GBP, CHF).forEach { + vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER) + } + val currencies = listOf("cHf", "gBp") + val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.`in`(currencies, false) } + val criteria = VaultCustomQueryCriteria(logicalExpression) + val results = vaultService.queryBy(criteria) + assertThat(results.states).hasSize(2) + } + } + + @Test + fun `logical operator case insensitive IN does not affect numbers`() { + database.transaction { + vaultFiller.fillWithSomeTestCash(AMOUNT(100, USD), notaryServices, 1, DUMMY_CASH_ISSUER) + vaultFiller.fillWithSomeTestCash(AMOUNT(200, CHF), notaryServices, 1, DUMMY_CASH_ISSUER) + vaultFiller.fillWithSomeTestCash(AMOUNT(50, GBP), notaryServices, 1, DUMMY_CASH_ISSUER) + val logicalExpression = builder { CashSchemaV1.PersistentCashState::pennies.`in`(listOf(10000L, 20000L), false) } + val criteria = VaultCustomQueryCriteria(logicalExpression) + val results = vaultService.queryBy(criteria) + assertThat(results.states).hasSize(2) + } + } + + @Test + fun `logical operator case insensitive NOT IN does not return results containing the same characters as the case insensitive strings`() { + database.transaction { + listOf(USD, GBP, CHF).forEach { + vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER) + } + val currencies = listOf("cHf", "gBp") + val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.notIn(currencies, false) } + val criteria = VaultCustomQueryCriteria(logicalExpression) + val results = vaultService.queryBy(criteria) + assertThat(results.states).hasSize(1) + } + } + + @Test + fun `logical operator case insensitive NOT_IN does not affect numbers`() { + database.transaction { + vaultFiller.fillWithSomeTestCash(AMOUNT(100, USD), notaryServices, 1, DUMMY_CASH_ISSUER) + vaultFiller.fillWithSomeTestCash(AMOUNT(200, CHF), notaryServices, 1, DUMMY_CASH_ISSUER) + vaultFiller.fillWithSomeTestCash(AMOUNT(50, GBP), notaryServices, 1, DUMMY_CASH_ISSUER) + val logicalExpression = builder { CashSchemaV1.PersistentCashState::pennies.notIn(listOf(10000L, 20000L), false) } + val criteria = VaultCustomQueryCriteria(logicalExpression) + val results = vaultService.queryBy(criteria) + assertThat(results.states).hasSize(1) + } + } + + @Test + fun `logical operator case insensitive LIKE`() { + database.transaction { + listOf(USD, GBP, CHF).forEach { + vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER) + } + val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.like("%bP", false) } // GPB + val criteria = VaultCustomQueryCriteria(logicalExpression) + val results = vaultService.queryBy(criteria) + assertThat(results.states).hasSize(1) + } + } + + @Test + fun `logical operator NOT LIKE does not return results containing the same characters as the case insensitive string`() { + database.transaction { + listOf(USD, GBP, CHF).forEach { + vaultFiller.fillWithSomeTestCash(AMOUNT(100, it), notaryServices, 1, DUMMY_CASH_ISSUER) + } + val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.notLike("%bP", false) } // GPB + val criteria = VaultCustomQueryCriteria(logicalExpression) + val results = vaultService.queryBy(criteria) + assertThat(results.states).hasSize(2) + } + } + @Test fun `aggregate functions without group clause`() { database.transaction { diff --git a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt index 09d61e64a6..33922035e2 100644 --- a/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt +++ b/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt @@ -16,11 +16,11 @@ import net.corda.core.internal.toX500Name import net.corda.core.utilities.seconds import net.corda.node.NodeRegistrationOption import net.corda.node.services.config.NodeConfiguration -import net.corda.nodeapi.internal.DevIdentityGenerator import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities +import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_ALIAS_PREFIX import net.corda.testing.core.ALICE_NAME import net.corda.testing.internal.stubs.CertificateStoreStubs import net.corda.testing.internal.createDevIntermediateCaCertPath @@ -168,7 +168,7 @@ class NetworkRegistrationHelperTest { assertThat(config.p2pSslOptions.keyStore.getOptional()).isNull() assertThat(config.p2pSslOptions.trustStore.getOptional()).isNull() - val serviceIdentityAlias = "${DevIdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX}-private-key" + val serviceIdentityAlias = "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key" nodeKeystore.run { assertFalse(contains(X509Utilities.CORDA_INTERMEDIATE_CA)) @@ -233,7 +233,7 @@ class NetworkRegistrationHelperTest { certService, config.certificatesDirectory / networkRootTrustStoreFileName, networkRootTrustStorePassword, - "${DevIdentityGenerator.DISTRIBUTED_NOTARY_ALIAS_PREFIX}-private-key", + "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key", CertRole.SERVICE_IDENTITY) else -> throw IllegalArgumentException("Unsupported cert role.") } diff --git a/tools/shell-cli/build.gradle b/tools/shell-cli/build.gradle index 45513f5f89..d4d510e4cb 100644 --- a/tools/shell-cli/build.gradle +++ b/tools/shell-cli/build.gradle @@ -2,9 +2,7 @@ description 'Corda Shell CLI' buildscript { repositories { - maven { - url "https://plugins.gradle.org/m2/" - } + gradlePluginPortal() } dependencies { classpath "com.github.jengelman.gradle.plugins:shadow:$shadow_version" @@ -12,15 +10,16 @@ buildscript { } apply plugin: 'application' +// We need to set mainClassName before applying the shadow plugin. +mainClassName = 'net.corda.tools.shell.StandaloneShellKt' + apply plugin: 'com.github.johnrengelman.shadow' apply plugin: 'net.corda.plugins.publish-utils' apply plugin: 'com.jfrog.artifactory' -mainClassName = 'net.corda.tools.shell.StandaloneShellKt' - dependencies { compile project(':tools:shell') - compile group: 'org.slf4j', name: 'slf4j-simple', version: slf4j_version + compile "org.slf4j:slf4j-simple:$slf4j_version" testCompile(project(':test-utils')) { exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl' @@ -36,10 +35,11 @@ shadowJar { } task buildShellCli(dependsOn: shadowJar) +assemble.dependsOn buildShellCli artifacts { publish shadowJar { - classifier "" + classifier = "" } } diff --git a/tools/shell/build.gradle b/tools/shell/build.gradle index 219fdaa055..13338150fc 100644 --- a/tools/shell/build.gradle +++ b/tools/shell/build.gradle @@ -61,24 +61,25 @@ dependencies { compile "com.jcabi:jcabi-manifests:1.1" // For logging, required for ANSIProgressRenderer. - compile "org.apache.logging.log4j:log4j-core:${log4j_version}" + compile "org.apache.logging.log4j:log4j-core:$log4j_version" // Unit testing helpers. testCompile "junit:junit:$junit_version" - testCompile "org.assertj:assertj-core:${assertj_version}" + testCompile "org.assertj:assertj-core:$assertj_version" testCompile project(':test-utils') testCompile project(':finance') - // Integration test helpers. - integrationTestCompile "junit:junit:$junit_version" - integrationTestCompile "org.assertj:assertj-core:${assertj_version}" - // Jsh: Testing SSH server. integrationTestCompile "com.jcraft:jsch:$jsch_version" integrationTestCompile project(':node-driver') } +tasks.withType(JavaCompile) { + // Resolves a Gradle warning about not scanning for pre-processors. + options.compilerArgs << '-proc:none' +} + task integrationTest(type: Test) { testClassesDirs = sourceSets.integrationTest.output.classesDirs classpath = sourceSets.integrationTest.runtimeClasspath