mirror of
https://github.com/corda/corda.git
synced 2024-12-20 05:28:21 +00:00
AttachmentCriteriaQuery class and infrastructure (#2022)
* Attachments metadata support
This commit is contained in:
parent
2a961b8e2c
commit
1a02c9a74f
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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>
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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``.
|
||||
|
@ -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
|
||||
--------
|
||||
|
||||
|
@ -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>()
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user