AttachmentCriteriaQuery class and infrastructure (#2022)

* Attachments metadata support
This commit is contained in:
Maksymilian Pawlak 2017-11-14 10:22:02 +00:00 committed by GitHub
parent 2a961b8e2c
commit 1a02c9a74f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 593 additions and 138 deletions

View File

@ -1364,12 +1364,14 @@ public final class net.corda.core.identity.IdentityUtils extends java.lang.Objec
@org.jetbrains.annotations.NotNull public abstract java.io.InputStream openAttachment(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public abstract Set partiesFromName(String, boolean)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.identity.Party partyFromKey(java.security.PublicKey)
@org.jetbrains.annotations.NotNull public abstract List queryAttachments(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentSort)
@org.jetbrains.annotations.NotNull public abstract List registeredFlows()
@net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed stateMachineRecordedTransactionMappingFeed()
@org.jetbrains.annotations.NotNull public abstract List stateMachineRecordedTransactionMappingSnapshot()
@net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.messaging.DataFeed stateMachinesFeed()
@org.jetbrains.annotations.NotNull public abstract List stateMachinesSnapshot()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash uploadAttachment(java.io.InputStream)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash uploadAttachmentWithMetadata(java.io.InputStream, String, String)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.Vault$Page vaultQuery(Class)
@net.corda.core.messaging.RPCReturnsObservables @org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.Vault$Page vaultQueryBy(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.PageSpecification, net.corda.core.node.services.vault.Sort, Class)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.Vault$Page vaultQueryByCriteria(net.corda.core.node.services.vault.QueryCriteria, Class)
@ -1548,7 +1550,9 @@ public @interface net.corda.core.messaging.RPCReturnsObservables
##
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.AttachmentStorage
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.crypto.SecureHash importAttachment(java.io.InputStream, String, String)
@org.jetbrains.annotations.Nullable public abstract net.corda.core.contracts.Attachment openAttachment(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public abstract List queryAttachments(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentSort)
##
public final class net.corda.core.node.services.AttachmentStorageKt extends java.lang.Object
##
@ -1837,6 +1841,76 @@ public final class net.corda.core.node.services.VaultServiceKt extends java.lang
public static net.corda.core.node.services.vault.AggregateFunctionType valueOf(String)
public static net.corda.core.node.services.vault.AggregateFunctionType[] values()
##
@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.AttachmentQueryCriteria extends java.lang.Object implements net.corda.core.node.services.vault.GenericQueryCriteria$ChainableQueryCriteria, net.corda.core.node.services.vault.GenericQueryCriteria
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.vault.AttachmentQueryCriteria and(net.corda.core.node.services.vault.AttachmentQueryCriteria)
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.vault.AttachmentQueryCriteria or(net.corda.core.node.services.vault.AttachmentQueryCriteria)
##
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.AttachmentQueryCriteria$AndComposition extends net.corda.core.node.services.vault.AttachmentQueryCriteria implements net.corda.core.node.services.vault.GenericQueryCriteria$ChainableQueryCriteria$AndVisitor
public <init>(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentQueryCriteria)
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.vault.AttachmentQueryCriteria getA()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.vault.AttachmentQueryCriteria getB()
@org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.AttachmentsQueryCriteriaParser)
##
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.AttachmentQueryCriteria$AttachmentsQueryCriteria extends net.corda.core.node.services.vault.AttachmentQueryCriteria
public <init>()
public <init>(net.corda.core.node.services.vault.ColumnPredicate)
public <init>(net.corda.core.node.services.vault.ColumnPredicate, net.corda.core.node.services.vault.ColumnPredicate)
public <init>(net.corda.core.node.services.vault.ColumnPredicate, net.corda.core.node.services.vault.ColumnPredicate, net.corda.core.node.services.vault.ColumnPredicate)
@org.jetbrains.annotations.Nullable public final net.corda.core.node.services.vault.ColumnPredicate component1()
@org.jetbrains.annotations.Nullable public final net.corda.core.node.services.vault.ColumnPredicate component2()
@org.jetbrains.annotations.Nullable public final net.corda.core.node.services.vault.ColumnPredicate component3()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.AttachmentQueryCriteria$AttachmentsQueryCriteria copy(net.corda.core.node.services.vault.ColumnPredicate, net.corda.core.node.services.vault.ColumnPredicate, net.corda.core.node.services.vault.ColumnPredicate)
public boolean equals(Object)
@org.jetbrains.annotations.Nullable public final net.corda.core.node.services.vault.ColumnPredicate getFilenameCondition()
@org.jetbrains.annotations.Nullable public final net.corda.core.node.services.vault.ColumnPredicate getUploadDateCondition()
@org.jetbrains.annotations.Nullable public final net.corda.core.node.services.vault.ColumnPredicate getUploaderCondition()
public int hashCode()
public String toString()
@org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.AttachmentsQueryCriteriaParser)
##
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.AttachmentQueryCriteria$OrComposition extends net.corda.core.node.services.vault.AttachmentQueryCriteria implements net.corda.core.node.services.vault.GenericQueryCriteria$ChainableQueryCriteria$OrVisitor
public <init>(net.corda.core.node.services.vault.AttachmentQueryCriteria, net.corda.core.node.services.vault.AttachmentQueryCriteria)
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.vault.AttachmentQueryCriteria getA()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.vault.AttachmentQueryCriteria getB()
@org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.AttachmentsQueryCriteriaParser)
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.AttachmentSort extends net.corda.core.node.services.vault.BaseSort
public <init>(Collection)
@org.jetbrains.annotations.NotNull public final Collection component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.AttachmentSort copy(Collection)
public boolean equals(Object)
@org.jetbrains.annotations.NotNull public final Collection getColumns()
public int hashCode()
public String toString()
##
public static final class net.corda.core.node.services.vault.AttachmentSort$AttachmentSortAttribute extends java.lang.Enum
protected <init>(String, int, String)
@org.jetbrains.annotations.NotNull public final String getColumnName()
public static net.corda.core.node.services.vault.AttachmentSort$AttachmentSortAttribute valueOf(String)
public static net.corda.core.node.services.vault.AttachmentSort$AttachmentSortAttribute[] values()
##
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.AttachmentSort$AttachmentSortColumn extends java.lang.Object
public <init>(net.corda.core.node.services.vault.AttachmentSort$AttachmentSortAttribute, net.corda.core.node.services.vault.Sort$Direction)
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.AttachmentSort$AttachmentSortAttribute component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Sort$Direction component2()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.AttachmentSort$AttachmentSortColumn copy(net.corda.core.node.services.vault.AttachmentSort$AttachmentSortAttribute, net.corda.core.node.services.vault.Sort$Direction)
public boolean equals(Object)
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Sort$Direction getDirection()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.AttachmentSort$AttachmentSortAttribute getSortAttribute()
public int hashCode()
public String toString()
##
public interface net.corda.core.node.services.vault.AttachmentsQueryCriteriaParser extends net.corda.core.node.services.vault.BaseQueryCriteriaParser
@org.jetbrains.annotations.NotNull public abstract Collection parseCriteria(net.corda.core.node.services.vault.AttachmentQueryCriteria$AttachmentsQueryCriteria)
##
public interface net.corda.core.node.services.vault.BaseQueryCriteriaParser
@org.jetbrains.annotations.NotNull public abstract Collection parse(net.corda.core.node.services.vault.GenericQueryCriteria, net.corda.core.node.services.vault.BaseSort)
@org.jetbrains.annotations.NotNull public abstract Collection parseAnd(net.corda.core.node.services.vault.GenericQueryCriteria, net.corda.core.node.services.vault.GenericQueryCriteria)
@org.jetbrains.annotations.NotNull public abstract Collection parseOr(net.corda.core.node.services.vault.GenericQueryCriteria, net.corda.core.node.services.vault.GenericQueryCriteria)
##
public abstract class net.corda.core.node.services.vault.BaseSort extends java.lang.Object
public <init>()
##
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.BinaryComparisonOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator
protected <init>(String, int)
public static net.corda.core.node.services.vault.BinaryComparisonOperator valueOf(String)
@ -2052,15 +2126,29 @@ public final class net.corda.core.node.services.VaultServiceKt extends java.lang
public static net.corda.core.node.services.vault.EqualityComparisonOperator valueOf(String)
public static net.corda.core.node.services.vault.EqualityComparisonOperator[] values()
##
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.vault.IQueryCriteriaParser
@org.jetbrains.annotations.NotNull public abstract Collection parse(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.Sort)
@org.jetbrains.annotations.NotNull public abstract Collection parseAnd(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.QueryCriteria)
public interface net.corda.core.node.services.vault.GenericQueryCriteria
@org.jetbrains.annotations.NotNull public abstract Collection visit(net.corda.core.node.services.vault.BaseQueryCriteriaParser)
##
public static interface net.corda.core.node.services.vault.GenericQueryCriteria$ChainableQueryCriteria
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.vault.GenericQueryCriteria and(net.corda.core.node.services.vault.GenericQueryCriteria)
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.vault.GenericQueryCriteria or(net.corda.core.node.services.vault.GenericQueryCriteria)
##
public static interface net.corda.core.node.services.vault.GenericQueryCriteria$ChainableQueryCriteria$AndVisitor extends net.corda.core.node.services.vault.GenericQueryCriteria
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.vault.GenericQueryCriteria getA()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.vault.GenericQueryCriteria getB()
@org.jetbrains.annotations.NotNull public abstract Collection visit(net.corda.core.node.services.vault.BaseQueryCriteriaParser)
##
public static interface net.corda.core.node.services.vault.GenericQueryCriteria$ChainableQueryCriteria$OrVisitor extends net.corda.core.node.services.vault.GenericQueryCriteria
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.vault.GenericQueryCriteria getA()
@org.jetbrains.annotations.NotNull public abstract net.corda.core.node.services.vault.GenericQueryCriteria getB()
@org.jetbrains.annotations.NotNull public abstract Collection visit(net.corda.core.node.services.vault.BaseQueryCriteriaParser)
##
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.vault.IQueryCriteriaParser extends net.corda.core.node.services.vault.BaseQueryCriteriaParser
@org.jetbrains.annotations.NotNull public abstract Collection parseCriteria(net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria)
@org.jetbrains.annotations.NotNull public abstract Collection parseCriteria(net.corda.core.node.services.vault.QueryCriteria$FungibleAssetQueryCriteria)
@org.jetbrains.annotations.NotNull public abstract Collection parseCriteria(net.corda.core.node.services.vault.QueryCriteria$LinearStateQueryCriteria)
@org.jetbrains.annotations.NotNull public abstract Collection parseCriteria(net.corda.core.node.services.vault.QueryCriteria$VaultCustomQueryCriteria)
@org.jetbrains.annotations.NotNull public abstract Collection parseCriteria(net.corda.core.node.services.vault.QueryCriteria$VaultQueryCriteria)
@org.jetbrains.annotations.NotNull public abstract Collection parseOr(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.QueryCriteria)
##
@net.corda.core.serialization.CordaSerializable @net.corda.core.DoNotImplement public final class net.corda.core.node.services.vault.LikenessOperator extends java.lang.Enum implements net.corda.core.node.services.vault.Operator
protected <init>(String, int)
@ -2087,10 +2175,15 @@ public final class net.corda.core.node.services.VaultServiceKt extends java.lang
public final boolean isDefault()
public String toString()
##
@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.QueryCriteria extends java.lang.Object
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria and(net.corda.core.node.services.vault.QueryCriteria)
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria or(net.corda.core.node.services.vault.QueryCriteria)
@org.jetbrains.annotations.NotNull public abstract Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser)
@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.QueryCriteria extends java.lang.Object implements net.corda.core.node.services.vault.GenericQueryCriteria$ChainableQueryCriteria, net.corda.core.node.services.vault.GenericQueryCriteria
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.vault.QueryCriteria and(net.corda.core.node.services.vault.QueryCriteria)
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.vault.QueryCriteria or(net.corda.core.node.services.vault.QueryCriteria)
##
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$AndComposition extends net.corda.core.node.services.vault.QueryCriteria implements net.corda.core.node.services.vault.GenericQueryCriteria$ChainableQueryCriteria$AndVisitor
public <init>(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.QueryCriteria)
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.vault.QueryCriteria getA()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.vault.QueryCriteria getB()
@org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser)
##
@net.corda.core.serialization.CordaSerializable public abstract static class net.corda.core.node.services.vault.QueryCriteria$CommonQueryCriteria extends net.corda.core.node.services.vault.QueryCriteria
public <init>()
@ -2151,6 +2244,12 @@ public final class net.corda.core.node.services.VaultServiceKt extends java.lang
public String toString()
@org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser)
##
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$OrComposition extends net.corda.core.node.services.vault.QueryCriteria implements net.corda.core.node.services.vault.GenericQueryCriteria$ChainableQueryCriteria$OrVisitor
public <init>(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.QueryCriteria)
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.vault.QueryCriteria getA()
@org.jetbrains.annotations.NotNull public net.corda.core.node.services.vault.QueryCriteria getB()
@org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser)
##
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.node.services.vault.QueryCriteria$SoftLockingCondition extends java.lang.Object
public <init>(net.corda.core.node.services.vault.QueryCriteria$SoftLockingType, List)
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.QueryCriteria$SoftLockingType component1()
@ -2234,7 +2333,7 @@ public final class net.corda.core.node.services.vault.QueryCriteriaUtils extends
public static final int DEFAULT_PAGE_SIZE = 200
public static final int MAX_PAGE_SIZE = 2147483647
##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.Sort extends java.lang.Object
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.node.services.vault.Sort extends net.corda.core.node.services.vault.BaseSort
public <init>(Collection)
@org.jetbrains.annotations.NotNull public final Collection component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Sort copy(Collection)

View File

@ -10,13 +10,11 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultQueryException
import net.corda.core.node.services.vault.DEFAULT_PAGE_SIZE
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.node.services.vault.*
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.Try
@ -222,6 +220,12 @@ interface CordaRPCOps : RPCOps {
/** Uploads a jar to the node, returns it's hash. */
fun uploadAttachment(jar: InputStream): SecureHash
/** Uploads a jar including metadata to the node, returns it's hash. */
fun uploadAttachmentWithMetadata(jar: InputStream, uploader:String, filename:String): SecureHash
/** Queries attachments metadata */
fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId>
/** Returns the node's current time. */
fun currentNodeTime(): Instant

View File

@ -3,6 +3,8 @@ package net.corda.core.node.services
import net.corda.core.DoNotImplement
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.vault.AttachmentQueryCriteria
import net.corda.core.node.services.vault.AttachmentSort
import java.io.IOException
import java.io.InputStream
import java.nio.file.FileAlreadyExistsException
@ -33,5 +35,23 @@ interface AttachmentStorage {
*/
@Throws(FileAlreadyExistsException::class, IOException::class)
fun importAttachment(jar: InputStream): AttachmentId
/**
* Inserts the given attachment with additional metadata, see [importAttachment] for input stream handling
* Extra parameters:
* @param uploader Uploader name
* @param filename Name of the file
*/
@Throws(FileAlreadyExistsException::class, IOException::class)
fun importAttachment(jar: InputStream, uploader: String, filename: String): AttachmentId
/**
* Searches attachment using given criteria and optional sort rules
* @param criteria Query criteria to use as a filter
* @param sorting Sorting definition, if not given, order is undefined
*
* @return List of AttachmentId of attachment matching criteria, sorted according to given sorting parameter
*/
fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort? = null): List<AttachmentId>
}

View File

@ -15,13 +15,38 @@ import java.time.Instant
import java.util.*
import javax.persistence.criteria.Predicate
interface GenericQueryCriteria<Q : GenericQueryCriteria<Q, *>, in P : BaseQueryCriteriaParser<Q, *, *>> {
fun visit(parser: P): Collection<Predicate>
interface ChainableQueryCriteria<Q : GenericQueryCriteria<Q, P>, in P : BaseQueryCriteriaParser<Q, P, *>> {
interface AndVisitor<Q : GenericQueryCriteria<Q, P>, in P : BaseQueryCriteriaParser<Q, P, S>, in S : BaseSort> : GenericQueryCriteria<Q,P> {
val a:Q
val b:Q
override fun visit(parser: P): Collection<Predicate> {
return parser.parseAnd(this.a, this.b)
}
}
interface OrVisitor<Q : GenericQueryCriteria<Q, P>, in P : BaseQueryCriteriaParser<Q, P, S>, in S : BaseSort> : GenericQueryCriteria<Q,P> {
val a:Q
val b:Q
override fun visit(parser: P): Collection<Predicate> {
return parser.parseOr(this.a, this.b)
}
}
infix fun and(criteria: Q): Q
infix fun or(criteria: Q): Q
}
}
/**
* Indexing assumptions:
* QueryCriteria assumes underlying schema tables are correctly indexed for performance.
*/
@CordaSerializable
sealed class QueryCriteria {
abstract fun visit(parser: IQueryCriteriaParser): Collection<Predicate>
sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaParser>, GenericQueryCriteria.ChainableQueryCriteria<QueryCriteria, IQueryCriteriaParser> {
@CordaSerializable
data class TimeCondition(val type: TimeInstantType, val predicate: ColumnPredicate<Instant>)
@ -121,19 +146,6 @@ sealed class QueryCriteria {
}
}
// enable composition of [QueryCriteria]
private data class AndComposition(val a: QueryCriteria, val b: QueryCriteria) : QueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseAnd(this.a, this.b)
}
}
private data class OrComposition(val a: QueryCriteria, val b: QueryCriteria) : QueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseOr(this.a, this.b)
}
}
// timestamps stored in the vault states table [VaultSchema.VaultStates]
@CordaSerializable
enum class TimeInstantType {
@ -141,18 +153,47 @@ sealed class QueryCriteria {
CONSUMED
}
infix fun and(criteria: QueryCriteria): QueryCriteria = AndComposition(this, criteria)
infix fun or(criteria: QueryCriteria): QueryCriteria = OrComposition(this, criteria)
class AndComposition(override val a: QueryCriteria, override val b: QueryCriteria): QueryCriteria(), GenericQueryCriteria.ChainableQueryCriteria.AndVisitor<QueryCriteria, IQueryCriteriaParser, Sort>
class OrComposition(override val a: QueryCriteria, override val b: QueryCriteria): QueryCriteria(), GenericQueryCriteria.ChainableQueryCriteria.OrVisitor<QueryCriteria, IQueryCriteriaParser, Sort>
override fun and(criteria: QueryCriteria): QueryCriteria = AndComposition(this, criteria)
override fun or(criteria: QueryCriteria): QueryCriteria = OrComposition(this, criteria)
}
@CordaSerializable
sealed class AttachmentQueryCriteria : GenericQueryCriteria<AttachmentQueryCriteria, AttachmentsQueryCriteriaParser>, GenericQueryCriteria.ChainableQueryCriteria<AttachmentQueryCriteria, AttachmentsQueryCriteriaParser> {
/**
* AttachmentsQueryCriteria:
*/
data class AttachmentsQueryCriteria @JvmOverloads constructor (val uploaderCondition: ColumnPredicate<String>? = null,
val filenameCondition: ColumnPredicate<String>? = null,
val uploadDateCondition: ColumnPredicate<Instant>? = null) : AttachmentQueryCriteria() {
override fun visit(parser: AttachmentsQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this)
}
}
class AndComposition(override val a: AttachmentQueryCriteria, override val b: AttachmentQueryCriteria): AttachmentQueryCriteria(), GenericQueryCriteria.ChainableQueryCriteria.AndVisitor<AttachmentQueryCriteria, AttachmentsQueryCriteriaParser, AttachmentSort>
class OrComposition(override val a: AttachmentQueryCriteria, override val b: AttachmentQueryCriteria): AttachmentQueryCriteria(), GenericQueryCriteria.ChainableQueryCriteria.OrVisitor<AttachmentQueryCriteria, AttachmentsQueryCriteriaParser, AttachmentSort>
override fun and(criteria: AttachmentQueryCriteria): AttachmentQueryCriteria = AndComposition(this, criteria)
override fun or(criteria: AttachmentQueryCriteria): AttachmentQueryCriteria = OrComposition(this, criteria)
}
interface BaseQueryCriteriaParser<Q: GenericQueryCriteria<Q, P>, in P: BaseQueryCriteriaParser<Q,P,S>, in S : BaseSort> {
fun parseOr(left: Q, right: Q): Collection<Predicate>
fun parseAnd(left: Q, right: Q): Collection<Predicate>
fun parse(criteria: Q, sorting: S? = null): Collection<Predicate>
}
@DoNotImplement
interface IQueryCriteriaParser {
interface IQueryCriteriaParser : BaseQueryCriteriaParser<QueryCriteria, IQueryCriteriaParser, Sort> {
fun parseCriteria(criteria: QueryCriteria.CommonQueryCriteria): Collection<Predicate>
fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection<Predicate>
fun parseCriteria(criteria: QueryCriteria.LinearStateQueryCriteria): Collection<Predicate>
fun <L : PersistentState> parseCriteria(criteria: QueryCriteria.VaultCustomQueryCriteria<L>): Collection<Predicate>
fun parseCriteria(criteria: QueryCriteria.VaultQueryCriteria): Collection<Predicate>
fun parseOr(left: QueryCriteria, right: QueryCriteria): Collection<Predicate>
fun parseAnd(left: QueryCriteria, right: QueryCriteria): Collection<Predicate>
fun parse(criteria: QueryCriteria, sorting: Sort? = null): Collection<Predicate>
}
interface AttachmentsQueryCriteriaParser : BaseQueryCriteriaParser<AttachmentQueryCriteria, AttachmentsQueryCriteriaParser, AttachmentSort>{
fun parseCriteria(criteria: AttachmentQueryCriteria.AttachmentsQueryCriteria): Collection<Predicate>
}

View File

@ -127,12 +127,14 @@ data class PageSpecification(val pageNumber: Int = -1, val pageSize: Int = DEFAU
val isDefault = (pageSize == DEFAULT_PAGE_SIZE && pageNumber == -1)
}
abstract class BaseSort
/**
* Sort allows specification of a set of entity attribute names and their associated directionality
* and null handling, to be applied upon processing a query specification.
*/
@CordaSerializable
data class Sort(val columns: Collection<SortColumn>) {
data class Sort(val columns: Collection<SortColumn>) : BaseSort() {
@CordaSerializable
enum class Direction {
ASC,
@ -177,6 +179,21 @@ data class Sort(val columns: Collection<SortColumn>) {
val direction: Sort.Direction = Sort.Direction.ASC)
}
@CordaSerializable
data class AttachmentSort(val columns: Collection<AttachmentSortColumn>) : BaseSort() {
enum class AttachmentSortAttribute(val columnName: String) {
INSERTION_DATE("insertion_date"),
UPLOADER("uploader"),
FILENAME("filename")
}
@CordaSerializable
data class AttachmentSortColumn(
val sortAttribute: AttachmentSortAttribute,
val direction: Sort.Direction = Sort.Direction.ASC)
}
@CordaSerializable
sealed class SortAttribute {
/**
@ -257,6 +274,11 @@ object Builder {
fun <R : Comparable<R>> between(from: R, to: R) = ColumnPredicate.Between(from, to)
fun <R : Comparable<R>> `in`(collection: Collection<R>) = ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection)
fun <R : Comparable<R>> notIn(collection: Collection<R>) = 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)
fun <R> isNull() = ColumnPredicate.NullExpression<R>(NullOperator.IS_NULL)
fun <R> isNotNull() = ColumnPredicate.NullExpression<R>(NullOperator.NOT_NULL)
fun <O> KProperty1<O, String?>.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string))
@JvmStatic

View File

@ -6,6 +6,9 @@ from the previous milestone release.
UNRELEASED
----------
* ``AttachmentStorage`` now allows providing metadata on attachments upload - username and filename, currently as plain
strings. Those can be then used for querying, utilizing ``queryAttachments`` method of the same interface.
* ``CordaRPCOps`` implementation now checks permissions for any function invocation, rather than just when starting flows.
* ``OpaqueBytes.bytes`` now returns a clone of its underlying ``ByteArray``, and has been redeclared as ``final``.

View File

@ -25,6 +25,13 @@ is also available for interactive use via the shell. To **upload** run:
``>>> run uploadAttachment jar: /path/to/the/file.jar``
or
``>>> run uploadAttachmentWithMetadata jar: /path/to/the/file.jar, uploader: myself, filename: original_name.jar``
to include the metadata with the attachment which can be used to find it later on. Note, that currently both uploader
and filename are just plain strings (there is no connection between uploader and the RPC users for example).
The file is uploaded, checked and if successful the hash of the file is returned. This is how the attachment is
identified inside the node.
@ -36,6 +43,28 @@ which will then ask you to provide a path to save the file to. To do the same th
can pass a simple ``InputStream`` or ``SecureHash`` to the ``uploadAttachment``/``openAttachment`` RPCs from
a JVM client.
Searching for attachments
-------------------------
Attachments metadata can be used to query, in the similar manner as :doc:`api-vault-query`.
``AttachmentQueryCriteria`` can be used to build a query, utilizing set of operations per column, namely:
* Binary logical (AND, OR)
* Comparison (LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL)
* Equality (EQUAL, NOT_EQUAL)
* Likeness (LIKE, NOT_LIKE)
* Nullability (IS_NULL, NOT_NULL)
* Collection based (IN, NOT_IN)
``And`` and ``or`` operators can be used to build queries of arbitrary complexity. Example of such query:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentStorageTest.kt
:language: kotlin
:start-after: DOCSTART AttachmentQueryExample1
:end-before: DOCEND AttachmentQueryExample1
:dedent: 12
Protocol
--------

View File

@ -13,13 +13,13 @@ import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine
import net.corda.core.messaging.*
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.node.services.vault.*
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor
import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.messaging.rpcContext
@ -177,6 +177,25 @@ internal class CordaRPCOpsImpl(
}
}
override fun uploadAttachmentWithMetadata(jar: InputStream, uploader:String, filename:String): SecureHash {
// TODO: this operation should not require an explicit transaction
return database.transaction {
services.attachments.importAttachment(jar, uploader, filename)
}
}
override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> {
try {
return database.transaction {
services.attachments.queryAttachments(query, sorting)
}
} catch (e: Exception) {
// log and rethrow exception so we keep a copy server side
log.error(e.message)
throw e.cause ?: e
}
}
override fun currentNodeTime(): Instant = Instant.now(services.clock)
override fun waitUntilNetworkReady(): CordaFuture<Void?> = services.networkMapCache.nodeReady
@ -264,5 +283,6 @@ internal class CordaRPCOpsImpl(
is StateMachineManager.Change.Removed -> StateMachineUpdate.Removed(change.logic.runId, change.result)
}
}
private val log = loggerFor<CordaRPCOpsImpl>()
}
}

View File

@ -10,11 +10,10 @@ import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.NodeState
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.node.services.vault.*
import net.corda.node.services.messaging.RpcContext
import net.corda.node.services.messaging.requireEitherPermission
import rx.Observable
@ -24,6 +23,14 @@ import java.security.PublicKey
// TODO change to KFunction reference after Kotlin fixes https://youtrack.jetbrains.com/issue/KT-12140
class RpcAuthorisationProxy(private val implementation: CordaRPCOps, private val context: () -> RpcContext, private val permissionsAllowing: (methodName: String, args: List<Any?>) -> Set<String>) : CordaRPCOps {
override fun uploadAttachmentWithMetadata(jar: InputStream, uploader: String, filename: String): SecureHash = guard("uploadAttachmentWithMetadata") {
implementation.uploadAttachmentWithMetadata(jar, uploader, filename)
}
override fun queryAttachments(query: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> = guard("queryAttachments") {
implementation.queryAttachments(query, sorting)
}
override fun stateMachinesSnapshot() = guard("stateMachinesSnapshot") {
implementation.stateMachinesSnapshot()
}

View File

@ -7,19 +7,26 @@ import com.google.common.hash.Hashing
import com.google.common.hash.HashingInputStream
import com.google.common.io.CountingInputStream
import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.*
import net.corda.core.internal.AbstractAttachment
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.vault.*
import net.corda.core.serialization.*
import net.corda.core.utilities.loggerFor
import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser
import net.corda.node.utilities.DatabaseTransactionManager
import net.corda.node.utilities.NODE_DATABASE_PREFIX
import net.corda.node.utilities.currentDBSession
import java.io.*
import java.lang.Exception
import java.nio.file.Paths
import java.time.Instant
import java.util.jar.JarInputStream
import javax.annotation.concurrent.ThreadSafe
import javax.persistence.*
import javax.persistence.Column
/**
* Stores attachments using Hibernate to database.
@ -37,7 +44,16 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single
@Column(name = "content")
@Lob
var content: ByteArray
var content: ByteArray,
@Column(name = "insertion_date", nullable = false, updatable = false)
var insertionDate: Instant = Instant.now(),
@Column(name = "uploader", updatable = false)
var uploader: String? = null,
@Column(name = "filename", updatable = false)
var filename: String? = null
) : Serializable
companion object {
@ -147,8 +163,16 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single
return null
}
override fun importAttachment(jar: InputStream): AttachmentId {
return import(jar, null, null)
}
override fun importAttachment(jar: InputStream, uploader: String, filename: String): AttachmentId {
return import(jar, uploader, filename)
}
// TODO: PLT-147: The attachment should be randomised to prevent brute force guessing and thus privacy leaks.
override fun importAttachment(jar: InputStream): SecureHash {
private fun import(jar: InputStream, uploader: String?, filename: String?): AttachmentId {
require(jar !is JarInputStream)
// Read the file into RAM, hashing it to find the ID as we go. The attachment must fit into memory.
@ -169,7 +193,7 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single
criteriaQuery.where(criteriaBuilder.equal(attachments.get<String>(DBAttachment::attId.name), id.toString()))
val count = session.createQuery(criteriaQuery).singleResult
if (count == 0L) {
val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes)
val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes, uploader = uploader, filename = filename)
session.save(attachment)
attachmentCount.inc()
@ -179,6 +203,30 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single
return id
}
override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> {
log.info("Attachment query criteria: $criteria, sorting: $sorting")
val session = DatabaseTransactionManager.current().session
val criteriaBuilder = session.criteriaBuilder
val criteriaQuery = criteriaBuilder.createQuery(DBAttachment::class.java)
val root = criteriaQuery.from(DBAttachment::class.java)
val criteriaParser = HibernateAttachmentQueryCriteriaParser(criteriaBuilder, criteriaQuery, root)
// parse criteria and build where predicates
criteriaParser.parse(criteria, sorting)
// prepare query for execution
val query = session.createQuery(criteriaQuery)
// execution
val results = query.resultList
return results.map { AttachmentId.parse(it.attId) }
}
private fun checkIsAValidJAR(stream: InputStream) {
// Just iterate over the entries with verification enabled: should be good enough to catch mistakes.
// Note that JarInputStream won't throw any kind of error at all if the file stream is in fact not

View File

@ -14,19 +14,161 @@ import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.toHexString
import net.corda.core.utilities.trace
import net.corda.node.services.persistence.NodeAttachmentService
import org.hibernate.query.criteria.internal.expression.LiteralExpression
import org.hibernate.query.criteria.internal.predicate.ComparisonPredicate
import org.hibernate.query.criteria.internal.predicate.InPredicate
import java.time.Instant
import java.util.*
import javax.persistence.Tuple
import javax.persistence.criteria.*
abstract class AbstractQueryCriteriaParser<Q : GenericQueryCriteria<Q,P>, in P: BaseQueryCriteriaParser<Q, P, S>, in S: BaseSort> : BaseQueryCriteriaParser<Q, P, S> {
abstract val criteriaBuilder: CriteriaBuilder
override fun parseOr(left: Q, right: Q): Collection<Predicate> {
val predicateSet = mutableSetOf<Predicate>()
val leftPredicates = parse(left)
val rightPredicates = parse(right)
val orPredicate = criteriaBuilder.or(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray())
predicateSet.add(orPredicate)
return predicateSet
}
override fun parseAnd(left: Q, right: Q): Collection<Predicate> {
val predicateSet = mutableSetOf<Predicate>()
val leftPredicates = parse(left)
val rightPredicates = parse(right)
val andPredicate = criteriaBuilder.and(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray())
predicateSet.add(andPredicate)
return predicateSet
}
protected fun columnPredicateToPredicate(column: Path<out Any?>, 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<Any?>? = uncheckedCast(columnPredicate.rightLiteral)
@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)
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<String?>
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<Comparable<Any?>?>
val fromLiteral: Comparable<Any?>? = uncheckedCast(columnPredicate.rightFromLiteral)
val toLiteral: Comparable<Any?>? = 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)
}
}
else -> throw VaultQueryException("Not expecting $columnPredicate")
}
}
}
class HibernateAttachmentQueryCriteriaParser(override val criteriaBuilder: CriteriaBuilder,
private val criteriaQuery: CriteriaQuery<NodeAttachmentService.DBAttachment>, val root: Root<NodeAttachmentService.DBAttachment>) :
AbstractQueryCriteriaParser<AttachmentQueryCriteria, AttachmentsQueryCriteriaParser, AttachmentSort>(), AttachmentsQueryCriteriaParser {
private companion object {
val log = loggerFor<HibernateAttachmentQueryCriteriaParser>()
}
init {
criteriaQuery.select(root)
}
override fun parse(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): Collection<Predicate> {
val predicateSet = criteria.visit(this)
sorting?.let {
if (sorting.columns.isNotEmpty())
parse(sorting)
}
criteriaQuery.where(*predicateSet.toTypedArray())
return predicateSet
}
private fun parse(sorting: AttachmentSort) {
log.trace { "Parsing sorting specification: $sorting" }
val orderCriteria = mutableListOf<Order>()
sorting.columns.map { (sortAttribute, direction) ->
when (direction) {
Sort.Direction.ASC -> orderCriteria.add(criteriaBuilder.asc(root.get<String>(sortAttribute.columnName)))
Sort.Direction.DESC -> orderCriteria.add(criteriaBuilder.desc(root.get<String>(sortAttribute.columnName)))
}
}
if (orderCriteria.isNotEmpty()) {
criteriaQuery.orderBy(orderCriteria)
}
}
override fun parseCriteria(criteria: AttachmentQueryCriteria.AttachmentsQueryCriteria): Collection<Predicate> {
log.trace { "Parsing AttachmentsQueryCriteria: $criteria" }
val predicateSet = mutableSetOf<Predicate>()
criteria.filenameCondition?.let {
predicateSet.add(columnPredicateToPredicate(root.get<String>("filename"), it))
}
criteria.uploaderCondition?.let {
predicateSet.add(columnPredicateToPredicate(root.get<String>("uploader"), it))
}
criteria.uploadDateCondition?.let {
predicateSet.add(columnPredicateToPredicate(root.get<Instant>("upload_date"), it))
}
return predicateSet
}
}
class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractState>,
val contractStateTypeMappings: Map<String, Set<String>>,
val criteriaBuilder: CriteriaBuilder,
override val criteriaBuilder: CriteriaBuilder,
val criteriaQuery: CriteriaQuery<Tuple>,
val vaultStates: Root<VaultSchemaV1.VaultStates>) : IQueryCriteriaParser {
val vaultStates: Root<VaultSchemaV1.VaultStates>) : AbstractQueryCriteriaParser<QueryCriteria, IQueryCriteriaParser, Sort>(), IQueryCriteriaParser {
private companion object {
val log = loggerFor<HibernateQueryCriteriaParser>()
}
@ -102,56 +244,6 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
}
}
private fun columnPredicateToPredicate(column: Path<out Any?>, 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<Any?>? = uncheckedCast(columnPredicate.rightLiteral)
@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)
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<String?>
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<Comparable<Any?>?>
val fromLiteral: Comparable<Any?>? = uncheckedCast(columnPredicate.rightFromLiteral)
val toLiteral: Comparable<Any?>? = 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)
}
}
else -> throw VaultQueryException("Not expecting $columnPredicate")
}
}
private fun <O> parseExpression(entityRoot: Root<O>, expression: CriteriaExpression<O, Boolean>, predicateSet: MutableSet<Predicate>) {
if (expression is CriteriaExpression.AggregateFunctionExpression<O, *>) {
@ -326,32 +418,6 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
return predicateSet
}
override fun parseOr(left: QueryCriteria, right: QueryCriteria): Collection<Predicate> {
log.trace { "Parsing OR QueryCriteria composition: $left OR $right" }
val predicateSet = mutableSetOf<Predicate>()
val leftPredicates = parse(left)
val rightPredicates = parse(right)
val orPredicate = criteriaBuilder.or(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray())
predicateSet.add(orPredicate)
return predicateSet
}
override fun parseAnd(left: QueryCriteria, right: QueryCriteria): Collection<Predicate> {
log.trace { "Parsing AND QueryCriteria composition: $left AND $right" }
val predicateSet = mutableSetOf<Predicate>()
val leftPredicates = parse(left)
val rightPredicates = parse(right)
val andPredicate = criteriaBuilder.and(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray())
predicateSet.add(andPredicate)
return predicateSet
}
override fun parse(criteria: QueryCriteria, sorting: Sort?): Collection<Predicate> {
val predicateSet = criteria.visit(this)

View File

@ -9,6 +9,10 @@ import net.corda.core.internal.read
import net.corda.core.internal.readAll
import net.corda.core.internal.write
import net.corda.core.internal.writeLines
import net.corda.core.node.services.vault.AttachmentQueryCriteria
import net.corda.core.node.services.vault.AttachmentSort
import net.corda.core.node.services.vault.Builder
import net.corda.core.node.services.vault.Sort
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase
@ -51,8 +55,7 @@ class NodeAttachmentStorageTest {
@Test
fun `insert and retrieve`() {
val testJar = makeTestJar()
val expectedHash = testJar.readAll().sha256()
val (testJar,expectedHash) = makeTestJar()
database.transaction {
val storage = NodeAttachmentService(MetricRegistry())
@ -77,10 +80,87 @@ class NodeAttachmentStorageTest {
}
}
@Test
fun `metadata can be used to search`() {
val (jarA,hashA) = makeTestJar()
val (jarB,hashB) = makeTestJar(listOf(Pair("file","content")))
val (jarC,hashC) = makeTestJar(listOf(Pair("magic_file","magic_content_puff")))
database.transaction {
val storage = NodeAttachmentService(MetricRegistry())
jarA.read { storage.importAttachment(it) }
jarB.read { storage.importAttachment(it, "uploaderB", "fileB.zip") }
jarC.read { storage.importAttachment(it, "uploaderC", "fileC.zip") }
assertEquals(
listOf(hashB),
storage.queryAttachments( AttachmentQueryCriteria.AttachmentsQueryCriteria( Builder.equal("uploaderB")))
)
assertEquals (
listOf(hashB, hashC),
storage.queryAttachments( AttachmentQueryCriteria.AttachmentsQueryCriteria( Builder.like ("%uploader%")))
)
}
}
@Test
fun `sorting and compound conditions work`() {
val (jarA,hashA) = makeTestJar(listOf(Pair("a","a")))
val (jarB,hashB) = makeTestJar(listOf(Pair("b","b")))
val (jarC,hashC) = makeTestJar(listOf(Pair("c","c")))
fun uploaderCondition(s:String) = AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal(s))
fun filenamerCondition(s:String) = AttachmentQueryCriteria.AttachmentsQueryCriteria(filenameCondition = Builder.equal(s))
fun filenameSort(direction: Sort.Direction) = AttachmentSort(listOf(AttachmentSort.AttachmentSortColumn(AttachmentSort.AttachmentSortAttribute.FILENAME, direction)))
database.transaction {
val storage = NodeAttachmentService(MetricRegistry())
jarA.read { storage.importAttachment(it, "complexA", "archiveA.zip") }
jarB.read { storage.importAttachment(it, "complexB", "archiveB.zip") }
jarC.read { storage.importAttachment(it, "complexC", "archiveC.zip") }
// DOCSTART AttachmentQueryExample1
assertEquals(
emptyList(),
storage.queryAttachments(
AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexA"))
.and(AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexB"))))
)
assertEquals(
listOf(hashA, hashB),
storage.queryAttachments(
AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexA"))
.or(AttachmentQueryCriteria.AttachmentsQueryCriteria(uploaderCondition = Builder.equal("complexB"))))
)
val complexCondition =
(uploaderCondition("complexB").and(filenamerCondition("archiveB.zip"))).or(filenamerCondition("archiveC.zip"))
// DOCEND AttachmentQueryExample1
assertEquals (
listOf(hashB, hashC),
storage.queryAttachments(complexCondition, sorting = filenameSort(Sort.Direction.ASC))
)
assertEquals (
listOf(hashC, hashB),
storage.queryAttachments(complexCondition, sorting = filenameSort(Sort.Direction.DESC))
)
}
}
@Ignore("We need to be able to restart nodes - make importing attachments idempotent?")
@Test
fun `duplicates not allowed`() {
val testJar = makeTestJar()
val (testJar,_) = makeTestJar()
database.transaction {
val storage = NodeAttachmentService(MetricRegistry())
testJar.read {
@ -96,7 +176,7 @@ class NodeAttachmentStorageTest {
@Test
fun `corrupt entry throws exception`() {
val testJar = makeTestJar()
val (testJar,_) = makeTestJar()
val id = database.transaction {
val storage = NodeAttachmentService(MetricRegistry())
val id = testJar.read { storage.importAttachment(it) }
@ -139,7 +219,7 @@ class NodeAttachmentStorageTest {
}
private var counter = 0
private fun makeTestJar(): Path {
private fun makeTestJar(extraEntries: List<Pair<String,String>> = emptyList()): Pair<Path, SecureHash> {
counter++
val file = fs.getPath("$counter.jar")
file.write {
@ -149,8 +229,12 @@ class NodeAttachmentStorageTest {
jar.closeEntry()
jar.putNextEntry(JarEntry("test2.txt"))
jar.write("Some more useful content".toByteArray())
extraEntries.forEach {
jar.putNextEntry(JarEntry(it.first))
jar.write(it.second.toByteArray())
}
jar.closeEntry()
}
return file
return Pair(file, file.readAll().sha256())
}
}

View File

@ -4,7 +4,10 @@ import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.internal.AbstractAttachment
import net.corda.core.node.services.AttachmentId
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.vault.AttachmentQueryCriteria
import net.corda.core.node.services.vault.AttachmentSort
import net.corda.core.serialization.SingletonSerializeAsToken
import java.io.ByteArrayOutputStream
import java.io.InputStream
@ -12,16 +15,8 @@ import java.util.HashMap
import java.util.jar.JarInputStream
class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
val files = HashMap<SecureHash, ByteArray>()
override fun openAttachment(id: SecureHash): Attachment? {
val f = files[id] ?: return null
return object : AbstractAttachment({ f }) {
override val id = id
}
}
override fun importAttachment(jar: InputStream): SecureHash {
override fun importAttachment(jar: InputStream): AttachmentId {
// JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here.
require(jar !is JarInputStream)
@ -37,4 +32,21 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
}
return sha256
}
override fun importAttachment(jar: InputStream, uploader: String, filename: String): AttachmentId {
return importAttachment(jar)
}
val files = HashMap<SecureHash, ByteArray>()
override fun openAttachment(id: SecureHash): Attachment? {
val f = files[id] ?: return null
return object : AbstractAttachment({ f }) {
override val id = id
}
}
override fun queryAttachments(criteria: AttachmentQueryCriteria, sorting: AttachmentSort?): List<AttachmentId> {
throw NotImplementedError("Querying for attachments not implemented")
}
}