Skip to content
Query DSL and data access utilities for Corda developers.
Kotlin Shell
Branch: master
Clone or download
manosbatsis
manosbatsis fixed #2
improved field wrappers
v0.6
Latest commit f229d54 Sep 15, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
docs settings up docs Aug 21, 2019
etc/license initial release Aug 19, 2019
gradle/wrapper initial release Aug 19, 2019
vaultaire-example fixed #2 Sep 15, 2019
vaultaire-processor improved fieldwrappers, added aggregate test Sep 14, 2019
vaultaire fixed #2 Sep 15, 2019
.gitignore initial release Aug 19, 2019
.travis.yml test travis Aug 19, 2019
CHANGELOG.md improved fieldwrappers, added aggregate test Sep 14, 2019
LICENSE Initial commit Aug 19, 2019
README.md fixed #2 Sep 15, 2019
build.gradle fixed #2 Sep 15, 2019
deploy_website.sh Fixed #8 handling of nullable fields Aug 22, 2019
gradle.properties initial release Aug 19, 2019
gradlew initial release Aug 19, 2019
gradlew.bat initial release Aug 19, 2019
mkdocs.yml adding analytics Aug 21, 2019
settings.gradle initial release Aug 19, 2019

README.md

Vaultaire Maven Central Build Status

Query DSL and data access utilities for Corda developers.

Installation

Add to your Cordapp's Gradle dependencies:

// apply the kapt plugin
apply plugin: 'kotlin-kapt'

dependencies{
    // Core dependency
    cordaCompile "com.github.manosbatsis.vaultaire:vaultaire:$vaultaire_version"
    // Annotation processing
    kapt "com.github.manosbatsis.vaultaire:vaultaire-processor:$vaultaire_version"

    // Corda dependencies etc.
    // ...

}    

The core module can also be useful outside a cordapp, e.g. in a Spring application interacting with Corda nodes via RPC:

    // Core dependency
    compile "com.github.manosbatsis.vaultaire:vaultaire:$vaultaire_version"

    // Corda dependencies etc.
    // ...

Query DSL

Vaultaire uses an annotation processor at build-time to create a query DSL for your contract/persistent states.

Quick Example

Consider the following Contract/Persistent States:

/** Book ContractState */
data class BookState(
    val publisher: Party,
    val author: Party,
    val title: String,
    val published: Date = Date()
) : ContractState, QueryableState {
     // ...
}


// Use Vaultaire's code generation!
@VaultaireGenerate(name = "booksQuery", constractStateType = BookState::class)
@Entity
@Table(name = "books")
data class PersistentBookState(
    @Column(name = "publisher")
    var publisher: String = "",
    @Column(name = "author")
    var author: String = "",
    @Column(name = "title")
    var title: String = "",
    @Column(name = "published")
    var published: Date
) : PersistentState()

Without Vaultaire

Before Vaultaire, you probably had to create query criteria with something like:

val query = VaultQueryCriteria(
        contractStateTypes = setOf(BookState::class.java),
        status = Vault.StateStatus.ALL
    )
    .and(VaultCustomQueryCriteria(PersistentBookState::publisher.equal("Corda Books Ltd.")))
    .and(VaultCustomQueryCriteria(PersistentBookState::title.equal("A book on Corda"))
        .or(VaultCustomQueryCriteria(PersistentBookState::author.notEqual("John Doe"))))

val sort = Sort(listOf(Sort.SortColumn(
    SortAttribute.Custom(PersistentBookState::class.java, "published"), Sort.Direction.DESC)))

queryBy(query, sort)

With Vaultaire DSL

With Vaultaire's @VaultaireGenerate and the generated DSL this becomes:

// Use the generated DSL to create query criteria
val query = booksQuery {
    status = Vault.StateStatus.ALL
    and {
        fields.publisher `==` "Corda Books Ltd."
        or {
            fields.title  `==` "A book on Corda"
            fields.author `!=` "John Doe"
        }
    }
    orderBy {
        fields.title sort DESC
    }
}.toCriteria()

queryBy(query.toCriteria(), query.toSort())

Create a DSL

To create a query DSL for your state after installing Vaultaire, annotate the corresponding PersistentState with @VaultaireGenerate:

// Use Vaultaire's DSL generation!
@VaultaireGenerate(
  // If you omit the name, the DSL function will be named by appending "Query"
  // to the decapitalized contract state name, e.g. "bookStateQuery"
  name = "booksQuery",
  constractStateType = BookState::class)
@Entity
@Table(name = "books")
data class PersistentBookState(
    // state properties...
) : PersistentState()

Query Settings

The generated DSL allows setting QueryCriteria.VaultQueryCriteria members. Here's an example using the defaults:

val query = booksQuery {
    // settings
    status = Vault.StateStatus.UNCONSUMED
    stateRefs = null
    notary = null
    softLockingCondition = null
    timeCondition = null
    relevancyStatus = Vault.RelevancyStatus.ALL
    constraintTypes = emptySet()
    constraints = emptySet()
    participants = null

    // criteria and sorting...
}

Adding Criteria

Query riteria are defined within the and / or functions. Both functions can be nested and mixed with criteria like:

val query = booksQuery {
    // settings...

    // criteria
    or { // Match at least one
        fields.foo1 `==` someValue
        and { // Match all
            fields.foo1.isNull()
            fields.foo2 `==` someOtherValue
            and {
                // ...
            }
            or {
                // ...
            }
        }
    }

    // sorting...
}

Adding Aggregates

Aggregates can be specified within the and / or functions:

val bookStateQuery = bookStateQuery {
    // settings...
    
    // criteria
    and {
        fields.title `like` "%Corda Foundation%"
        fields.genre `==` BookContract.BookGenre.SCIENCE_FICTION
    }
    // aggregates
    aggregate {
        // add some aggregates
        fields.externalId.count()
        fields.id.count()
        fields.editions.sum()
        fields.price.min()
        fields.price.avg()
        fields.price.max()
    }
}

Note: Corda paged queries can include either query results or "other" results based on the above aggregates. For that purpose, the toCriteria functions accepts an optional boolean to ignore aggregates, thus allowing the reuse of the same query to obtain either paged state or aggregate results.

Accessing Fields

Fields can be accessed via the generated DSL's fields object within and, or, or orderBy functions using dot notation e.g. fields.foo.

You can also retrieve fields by name with e.g. fields["foo"] or fields.get("foo") and use non typesafe functions like _equal, _notEqual, _like, _notLike but this may change in a future release.

Functions and Operators

Name Aliases Examples
isNull fields.foo.isNull()
notNull fields.foo.notNull()
equal `==` fields.foo `==` bar
fields.foo equal bar
fields.foo.equal(bar)
notEqual `!=` fields.foo `!=` bar
fields.foo notEqual bar
fields.foo.notEqual(bar)
lessThan lt fields.foo lt bar
fields.foo lessThan bar
fields.foo.lessThan(bar)
lessThanOrEqual lte fields.foo lte bar
fields.foo lessThanOrEqual bar
fields.foo.lessThanOrEqual(bar)
greaterThan gt fields.foo gt bar
fields.foo greaterThan bar
fields.foo.greaterThan(bar)
greaterThanOrEqual gte fields.foo gte bar
fields.foo greaterThanOrEqual bar
fields.foo.greaterThanOrEqual(bar)
between btw fields.foo btw Pair(bar1, bar2)
fields.foo between Pair(bar1, bar2)
fields.foo.between(bar1, bar2)
like fields.foo like bar
fields.foo.like(bar)
notLike fields.foo notLike bar
fields.foo.notLike(bar)
isIn `in` fields.foo `in` bars
fields.foo isIn bars
fields.foo.isIn(bars)
notIn `!in` fields.foo `!in` bars
fields.foo notIn bars
fields.foo.notIn(bars)

Aggregate Functions

Name Examples
avg fields.foo.avg()
fields.foo.avg(groupByColumns)
fields.foo.avg(groupByColumns, sortDirection)
count fields.foo.count()
max fields.foo.max()
fields.foo.max(groupByColumns)
fields.foo.max(groupByColumns, sortDirection)
min fields.foo.min()
fields.foo.min(groupByColumns)
fields.foo.min(groupByColumns, sortDirection)
sum fields.foo.sum()
fields.foo.sum(groupByColumns)
fields.foo.sum(groupByColumns, sortDirection)

Sorting

Sorting is defined using the orderBy function:

val criteria = bookConditions {
    // settings and criteria...

    // sorting
    orderBy {
        fields.title sort ASC
        fields.published sort DESC
    }
}

State Services

Vaultaire's StateService provide an interface for querying states and tracking events from the Vault (queryBy, trackBy), while decoupling data access or business logic code from Corda's ServiceHub and CordaRPCOps.

Generated State Service

Vaultaire's annotation processor will automatically subclass ExtendedStateService to generate an Fields aware state service service per annotated element. The generated service name is "${contractStateTypeName}Service":

// Create an instance of the generated service type
val bookStateService = BookStateService(
    serviceHub,        // Service hub or RPC ops
    serviceDefaults)   // Optional: criteria, paging, sort defaults

// query the vault for books
val searchResults = bookStateService.queryBy(
    criteria, paging, Pair("published", DESC), Pair("title", DESC))

Custom Services

You can also subclass BasicStateService, ExtendedStateService or even generated service types
to create custom components.

/** Extend the generated [BookStateService] */
class MyExtendedBookStateService(
        delegate: StateServiceDelegate<BookState>
) : BookStateService(delegate){

    // Add the appropriate constructors
    // to initialize per delegate type:

    /** [CordaRPCOps]-based constructor */
    constructor(
            rpcOps: CordaRPCOps, defaults: StateServiceDefaults = StateServiceDefaults()
    ) : this(StateServiceRpcDelegate(rpcOps, BookState::class.java, defaults))

    /** [ServiceHub]-based constructor */
    constructor(
            serviceHub: ServiceHub, defaults: StateServiceDefaults = StateServiceDefaults()
    ) : this(StateServiceHubDelegate(serviceHub, BookState::class.java, defaults))

    // Custom business methods...
}

Credits

  • The following projects where of great help in creating my first DSL: AutoDsl, SqlDsl and of course the amazing KotlinPoet
You can’t perform that action at this time.