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
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 java.io.InputStream openAttachment(net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public abstract Set partiesFromName(String, boolean) @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.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() @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() @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() @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() @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 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 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) @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) @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) @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 @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)
@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.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 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 valueOf(String)
public static net.corda.core.node.services.vault.AggregateFunctionType[] values() 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 @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) protected <init>(String, int)
public static net.corda.core.node.services.vault.BinaryComparisonOperator valueOf(String) 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 valueOf(String)
public static net.corda.core.node.services.vault.EqualityComparisonOperator[] values() public static net.corda.core.node.services.vault.EqualityComparisonOperator[] values()
## ##
@net.corda.core.DoNotImplement public interface net.corda.core.node.services.vault.IQueryCriteriaParser public interface net.corda.core.node.services.vault.GenericQueryCriteria
@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 visit(net.corda.core.node.services.vault.BaseQueryCriteriaParser)
@org.jetbrains.annotations.NotNull public abstract Collection parseAnd(net.corda.core.node.services.vault.QueryCriteria, net.corda.core.node.services.vault.QueryCriteria) ##
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$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$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$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$VaultCustomQueryCriteria)
@org.jetbrains.annotations.NotNull public abstract Collection parseCriteria(net.corda.core.node.services.vault.QueryCriteria$VaultQueryCriteria) @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 @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) 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 final boolean isDefault()
public String toString() public String toString()
## ##
@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.node.services.vault.QueryCriteria extends java.lang.Object @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 final 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 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 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 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 @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>() public <init>()
@ -2151,6 +2244,12 @@ public final class net.corda.core.node.services.VaultServiceKt extends java.lang
public String toString() public String toString()
@org.jetbrains.annotations.NotNull public Collection visit(net.corda.core.node.services.vault.IQueryCriteriaParser) @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 @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) 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() @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 DEFAULT_PAGE_SIZE = 200
public static final int MAX_PAGE_SIZE = 2147483647 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) public <init>(Collection)
@org.jetbrains.annotations.NotNull public final Collection component1() @org.jetbrains.annotations.NotNull public final Collection component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.node.services.vault.Sort copy(Collection) @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.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo 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.NetworkMapCache
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultQueryException import net.corda.core.node.services.VaultQueryException
import net.corda.core.node.services.vault.DEFAULT_PAGE_SIZE 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.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.Try import net.corda.core.utilities.Try
@ -222,6 +220,12 @@ interface CordaRPCOps : RPCOps {
/** Uploads a jar to the node, returns it's hash. */ /** Uploads a jar to the node, returns it's hash. */
fun uploadAttachment(jar: InputStream): SecureHash 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. */ /** Returns the node's current time. */
fun currentNodeTime(): Instant fun currentNodeTime(): Instant

View File

@ -3,6 +3,8 @@ package net.corda.core.node.services
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.contracts.Attachment import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash 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.IOException
import java.io.InputStream import java.io.InputStream
import java.nio.file.FileAlreadyExistsException import java.nio.file.FileAlreadyExistsException
@ -33,5 +35,23 @@ interface AttachmentStorage {
*/ */
@Throws(FileAlreadyExistsException::class, IOException::class) @Throws(FileAlreadyExistsException::class, IOException::class)
fun importAttachment(jar: InputStream): AttachmentId 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 java.util.*
import javax.persistence.criteria.Predicate 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: * Indexing assumptions:
* QueryCriteria assumes underlying schema tables are correctly indexed for performance. * QueryCriteria assumes underlying schema tables are correctly indexed for performance.
*/ */
@CordaSerializable @CordaSerializable
sealed class QueryCriteria { sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaParser>, GenericQueryCriteria.ChainableQueryCriteria<QueryCriteria, IQueryCriteriaParser> {
abstract fun visit(parser: IQueryCriteriaParser): Collection<Predicate>
@CordaSerializable @CordaSerializable
data class TimeCondition(val type: TimeInstantType, val predicate: ColumnPredicate<Instant>) 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] // timestamps stored in the vault states table [VaultSchema.VaultStates]
@CordaSerializable @CordaSerializable
enum class TimeInstantType { enum class TimeInstantType {
@ -141,18 +153,47 @@ sealed class QueryCriteria {
CONSUMED CONSUMED
} }
infix fun and(criteria: QueryCriteria): QueryCriteria = AndComposition(this, criteria) class AndComposition(override val a: QueryCriteria, override val b: QueryCriteria): QueryCriteria(), GenericQueryCriteria.ChainableQueryCriteria.AndVisitor<QueryCriteria, IQueryCriteriaParser, Sort>
infix fun or(criteria: QueryCriteria): QueryCriteria = OrComposition(this, criteria) 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 @DoNotImplement
interface IQueryCriteriaParser { interface IQueryCriteriaParser : BaseQueryCriteriaParser<QueryCriteria, IQueryCriteriaParser, Sort> {
fun parseCriteria(criteria: QueryCriteria.CommonQueryCriteria): Collection<Predicate> fun parseCriteria(criteria: QueryCriteria.CommonQueryCriteria): Collection<Predicate>
fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection<Predicate> fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection<Predicate>
fun parseCriteria(criteria: QueryCriteria.LinearStateQueryCriteria): Collection<Predicate> fun parseCriteria(criteria: QueryCriteria.LinearStateQueryCriteria): Collection<Predicate>
fun <L : PersistentState> parseCriteria(criteria: QueryCriteria.VaultCustomQueryCriteria<L>): Collection<Predicate> fun <L : PersistentState> parseCriteria(criteria: QueryCriteria.VaultCustomQueryCriteria<L>): Collection<Predicate>
fun parseCriteria(criteria: QueryCriteria.VaultQueryCriteria): 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) 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 * 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. * and null handling, to be applied upon processing a query specification.
*/ */
@CordaSerializable @CordaSerializable
data class Sort(val columns: Collection<SortColumn>) { data class Sort(val columns: Collection<SortColumn>) : BaseSort() {
@CordaSerializable @CordaSerializable
enum class Direction { enum class Direction {
ASC, ASC,
@ -177,6 +179,21 @@ data class Sort(val columns: Collection<SortColumn>) {
val direction: Sort.Direction = Sort.Direction.ASC) 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 @CordaSerializable
sealed class SortAttribute { 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>> 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>> `in`(collection: Collection<R>) = ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection)
fun <R : Comparable<R>> notIn(collection: Collection<R>) = ColumnPredicate.CollectionExpression(CollectionOperator.NOT_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)) fun <O> KProperty1<O, String?>.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string))
@JvmStatic @JvmStatic

View File

@ -6,6 +6,9 @@ from the previous milestone release.
UNRELEASED 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. * ``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``. * ``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`` ``>>> 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 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. 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 can pass a simple ``InputStream`` or ``SecureHash`` to the ``uploadAttachment``/``openAttachment`` RPCs from
a JVM client. 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 Protocol
-------- --------

View File

@ -13,13 +13,13 @@ import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine import net.corda.core.internal.FlowStateMachine
import net.corda.core.messaging.* import net.corda.core.messaging.*
import net.corda.core.node.NodeInfo 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.NetworkMapCache
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.PageSpecification import net.corda.core.node.services.vault.*
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.getOrThrow 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.FlowStarter
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.messaging.rpcContext 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 currentNodeTime(): Instant = Instant.now(services.clock)
override fun waitUntilNetworkReady(): CordaFuture<Void?> = services.networkMapCache.nodeReady 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) 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.DataFeed
import net.corda.core.messaging.NodeState import net.corda.core.messaging.NodeState
import net.corda.core.node.NodeInfo 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.NetworkMapCache
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.PageSpecification import net.corda.core.node.services.vault.*
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.node.services.messaging.RpcContext import net.corda.node.services.messaging.RpcContext
import net.corda.node.services.messaging.requireEitherPermission import net.corda.node.services.messaging.requireEitherPermission
import rx.Observable 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 // 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 { 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") { override fun stateMachinesSnapshot() = guard("stateMachinesSnapshot") {
implementation.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.hash.HashingInputStream
import com.google.common.io.CountingInputStream import com.google.common.io.CountingInputStream
import net.corda.core.CordaRuntimeException import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.*
import net.corda.core.internal.AbstractAttachment import net.corda.core.internal.AbstractAttachment
import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash 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.AttachmentStorage
import net.corda.core.node.services.vault.*
import net.corda.core.serialization.* import net.corda.core.serialization.*
import net.corda.core.utilities.loggerFor 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.NODE_DATABASE_PREFIX
import net.corda.node.utilities.currentDBSession import net.corda.node.utilities.currentDBSession
import java.io.* import java.io.*
import java.lang.Exception
import java.nio.file.Paths import java.nio.file.Paths
import java.time.Instant
import java.util.jar.JarInputStream import java.util.jar.JarInputStream
import javax.annotation.concurrent.ThreadSafe import javax.annotation.concurrent.ThreadSafe
import javax.persistence.* import javax.persistence.*
import javax.persistence.Column
/** /**
* Stores attachments using Hibernate to database. * Stores attachments using Hibernate to database.
@ -37,7 +44,16 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single
@Column(name = "content") @Column(name = "content")
@Lob @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 ) : Serializable
companion object { companion object {
@ -147,8 +163,16 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single
return null 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. // 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) require(jar !is JarInputStream)
// Read the file into RAM, hashing it to find the ID as we go. The attachment must fit into memory. // 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())) criteriaQuery.where(criteriaBuilder.equal(attachments.get<String>(DBAttachment::attId.name), id.toString()))
val count = session.createQuery(criteriaQuery).singleResult val count = session.createQuery(criteriaQuery).singleResult
if (count == 0L) { 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) session.save(attachment)
attachmentCount.inc() attachmentCount.inc()
@ -179,6 +203,30 @@ class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, Single
return id 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) { private fun checkIsAValidJAR(stream: InputStream) {
// Just iterate over the entries with verification enabled: should be good enough to catch mistakes. // 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 // 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.loggerFor
import net.corda.core.utilities.toHexString import net.corda.core.utilities.toHexString
import net.corda.core.utilities.trace 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.expression.LiteralExpression
import org.hibernate.query.criteria.internal.predicate.ComparisonPredicate import org.hibernate.query.criteria.internal.predicate.ComparisonPredicate
import org.hibernate.query.criteria.internal.predicate.InPredicate import org.hibernate.query.criteria.internal.predicate.InPredicate
import java.time.Instant
import java.util.* import java.util.*
import javax.persistence.Tuple import javax.persistence.Tuple
import javax.persistence.criteria.* 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>, class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractState>,
val contractStateTypeMappings: Map<String, Set<String>>, val contractStateTypeMappings: Map<String, Set<String>>,
val criteriaBuilder: CriteriaBuilder, override val criteriaBuilder: CriteriaBuilder,
val criteriaQuery: CriteriaQuery<Tuple>, val criteriaQuery: CriteriaQuery<Tuple>,
val vaultStates: Root<VaultSchemaV1.VaultStates>) : IQueryCriteriaParser { val vaultStates: Root<VaultSchemaV1.VaultStates>) : AbstractQueryCriteriaParser<QueryCriteria, IQueryCriteriaParser, Sort>(), IQueryCriteriaParser {
private companion object { private companion object {
val log = loggerFor<HibernateQueryCriteriaParser>() 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>) { private fun <O> parseExpression(entityRoot: Root<O>, expression: CriteriaExpression<O, Boolean>, predicateSet: MutableSet<Predicate>) {
if (expression is CriteriaExpression.AggregateFunctionExpression<O, *>) { if (expression is CriteriaExpression.AggregateFunctionExpression<O, *>) {
@ -326,32 +418,6 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
return predicateSet 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> { override fun parse(criteria: QueryCriteria, sorting: Sort?): Collection<Predicate> {
val predicateSet = criteria.visit(this) 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.readAll
import net.corda.core.internal.write import net.corda.core.internal.write
import net.corda.core.internal.writeLines 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.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase import net.corda.node.utilities.configureDatabase
@ -51,8 +55,7 @@ class NodeAttachmentStorageTest {
@Test @Test
fun `insert and retrieve`() { fun `insert and retrieve`() {
val testJar = makeTestJar() val (testJar,expectedHash) = makeTestJar()
val expectedHash = testJar.readAll().sha256()
database.transaction { database.transaction {
val storage = NodeAttachmentService(MetricRegistry()) 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?") @Ignore("We need to be able to restart nodes - make importing attachments idempotent?")
@Test @Test
fun `duplicates not allowed`() { fun `duplicates not allowed`() {
val testJar = makeTestJar() val (testJar,_) = makeTestJar()
database.transaction { database.transaction {
val storage = NodeAttachmentService(MetricRegistry()) val storage = NodeAttachmentService(MetricRegistry())
testJar.read { testJar.read {
@ -96,7 +176,7 @@ class NodeAttachmentStorageTest {
@Test @Test
fun `corrupt entry throws exception`() { fun `corrupt entry throws exception`() {
val testJar = makeTestJar() val (testJar,_) = makeTestJar()
val id = database.transaction { val id = database.transaction {
val storage = NodeAttachmentService(MetricRegistry()) val storage = NodeAttachmentService(MetricRegistry())
val id = testJar.read { storage.importAttachment(it) } val id = testJar.read { storage.importAttachment(it) }
@ -139,7 +219,7 @@ class NodeAttachmentStorageTest {
} }
private var counter = 0 private var counter = 0
private fun makeTestJar(): Path { private fun makeTestJar(extraEntries: List<Pair<String,String>> = emptyList()): Pair<Path, SecureHash> {
counter++ counter++
val file = fs.getPath("$counter.jar") val file = fs.getPath("$counter.jar")
file.write { file.write {
@ -149,8 +229,12 @@ class NodeAttachmentStorageTest {
jar.closeEntry() jar.closeEntry()
jar.putNextEntry(JarEntry("test2.txt")) jar.putNextEntry(JarEntry("test2.txt"))
jar.write("Some more useful content".toByteArray()) jar.write("Some more useful content".toByteArray())
extraEntries.forEach {
jar.putNextEntry(JarEntry(it.first))
jar.write(it.second.toByteArray())
}
jar.closeEntry() 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.SecureHash
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.internal.AbstractAttachment 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.AttachmentStorage
import net.corda.core.node.services.vault.AttachmentQueryCriteria
import net.corda.core.node.services.vault.AttachmentSort
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
@ -12,16 +15,8 @@ import java.util.HashMap
import java.util.jar.JarInputStream import java.util.jar.JarInputStream
class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() { class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
val files = HashMap<SecureHash, ByteArray>()
override fun openAttachment(id: SecureHash): Attachment? { override fun importAttachment(jar: InputStream): AttachmentId {
val f = files[id] ?: return null
return object : AbstractAttachment({ f }) {
override val id = id
}
}
override fun importAttachment(jar: InputStream): SecureHash {
// JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here. // JIS makes read()/readBytes() return bytes of the current file, but we want to hash the entire container here.
require(jar !is JarInputStream) require(jar !is JarInputStream)
@ -37,4 +32,21 @@ class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
} }
return sha256 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")
}
} }