io.p8e.ContractManager.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of p8e-sdk Show documentation
Show all versions of p8e-sdk Show documentation
A collection of services and libraries that iteract and run Provenance Java based contracts.
package io.p8e
import arrow.core.Either
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.protobuf.Message
import com.google.protobuf.Timestamp
import com.google.protobuf.util.Timestamps
import io.grpc.ManagedChannel
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder
import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder
import io.grpc.netty.shaded.io.netty.channel.ChannelOption
import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory
import io.p8e.async.EnvelopeEventObserver
import io.p8e.async.HeartbeatConnectionKey
import io.p8e.async.HeartbeatManagerRunnable
import io.p8e.async.HeartbeatRunnable
import io.p8e.client.FactSnapshot
import io.p8e.client.P8eClient
import io.p8e.client.RemoteClient
import io.p8e.contracts.ContractHash
import io.p8e.exception.P8eError
import io.p8e.exception.p8eError
import io.p8e.extension.scopeSpecificationNames
import io.p8e.functional.*
import io.p8e.grpc.Constant
import io.p8e.grpc.client.AuthenticationClient
import io.p8e.grpc.client.ChallengeResponseInterceptor
import io.p8e.grpc.observers.CompleteState
import io.p8e.grpc.observers.QueueingStreamObserverSender
import io.p8e.index.client.IndexClient
import io.p8e.proto.*
import io.p8e.proto.Common.DefinitionSpec.Type.FACT
import io.p8e.proto.Common.Location
import io.p8e.proto.Common.ProvenanceReference
import io.p8e.proto.ContractScope.*
import io.p8e.proto.ContractScope.Envelope
import io.p8e.proto.ContractScope.Envelope.Status
import io.p8e.proto.ContractSpecs.ContractSpec
import io.p8e.proto.ContractSpecs.PartyType
import io.p8e.proto.Envelope.EnvelopeEvent
import io.p8e.proto.Envelope.EnvelopeEvent.EventType
import io.p8e.proxy.Contract
import io.p8e.proxy.ContractError
import io.p8e.spec.ContractSpecMapper
import io.p8e.spec.ContractSpecMapper.newContract
import io.p8e.spec.P8eContract
import io.p8e.util.*
import io.p8e.util.configureProvenance
import io.provenance.p8e.shared.extension.logger
import org.apache.commons.logging.LogFactory
import org.apache.commons.logging.impl.Log4JLogger
import java.io.Closeable
import java.io.File
import java.net.URI
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import java.time.Duration
import java.time.OffsetDateTime
import java.util.ServiceLoader
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ForkJoinPool
import java.util.concurrent.TimeUnit
import java.util.logging.Level
import java.util.logging.Logger
class ContractManager(
val client: P8eClient,
val indexClient: IndexClient,
val keyPair: KeyPair,
val heartbeatLoggingThreshold: Duration = Duration.ofSeconds(10),
): Closeable {
companion object {
private val watchLock = Object()
private val objectMapper = ObjectMapper().configureProvenance()
private var channel: ManagedChannel? = null
fun create(hexKey: String, url: String? = null, deadlineMs: Long = 60000) = create(hexKey.toJavaPrivateKey(), url, deadlineMs)
fun create(privateKey: PrivateKey, url: String? = null, deadlineMs: Long = 60000): ContractManager {
val keyPair = privateKey.let {
KeyPair(it.computePublicKey(), it)
}
return create(keyPair, url, deadlineMs)
}
/**
* Create a new ContractManager for a given Party
*/
fun create(keyPair: KeyPair, url: String? = null, deadlineMs: Long = 60000): ContractManager {
val apiUrl = url ?: System.getenv("API_URL") ?: "http://localhost:8080/engine"
val uri = URI(apiUrl)
val customTrustStore = System.getenv("TRUST_STORE_PATH")?.let(::File)
channel = NettyChannelBuilder.forAddress(uri.host, uri.port)
.also {
if (uri.scheme == "grpcs") {
it.useTransportSecurity()
if (customTrustStore != null) {
val context =
GrpcSslContexts.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE)
.build()
when (it) {
is NettyChannelBuilder -> it.sslContext(context)
}
}
} else {
it.usePlaintext()
}
}.maxInboundMessageSize(Constant.MAX_MESSAGE_SIZE) // Set max inbound to 200MBB
.executor(ForkJoinPool.commonPool())
.idleTimeout(5, TimeUnit.MINUTES)
.keepAliveTime(60, TimeUnit.SECONDS)
.keepAliveTimeout(20, TimeUnit.SECONDS)
.initialFlowControlWindow(NettyServerBuilder.DEFAULT_FLOW_CONTROL_WINDOW * 32)
.withOption(ChannelOption.SO_SNDBUF, 1024 * 1024 * 16)
.withOption(ChannelOption.SO_RCVBUF, 1024 * 1024 * 16)
.build()
val interceptor = ChallengeResponseInterceptor(
keyPair,
AuthenticationClient(
channel!!,
deadlineMs
)
)
val index = IndexClient(channel!!, interceptor, deadlineMs, objectMapper)
return RemoteClient(keyPair.public, channel!!, interceptor, index, deadlineMs).let {
ContractManager(it, index, keyPair)
}
}
}
private val factPojoHydrator = FactPojoHydrator(client)
private val contractHashes = ServiceLoader.load(ContractHash::class.java).toList()
private val protoHashes = ServiceLoader.load(ProtoHash::class.java).toList()
private val queuers = ConcurrentHashMap, QueueingStreamObserverSender>()
private val handlers = ConcurrentHashMap, ContractHandlers>()
private val publicKey = keyPair.public
data class ContractHandlers(
val requestHandler: ContractEventHandler,
val stepCompletionHandler: ContractEventHandler,
val errorHandler: ContractErrorHandler
)
private val desiredHeartbeatConnections = ConcurrentHashMap EnvelopeEventObserver>()
private val actualHeartbeatConnections = ConcurrentHashMap()
private val heartbeatExecutor = ThreadPoolFactory.newScheduledThreadPool(1, "heartbeat-%d")
private val heartbeatManagerExecutor = ThreadPoolFactory.newScheduledThreadPool(1, "heartbeat-manager-%d")
init {
val logger = LogFactory.getLog("org.apache.http.wire")
when (logger) {
is Log4JLogger -> logger.logger.level = org.apache.log4j.Level.INFO
is Logger -> logger.level = Level.INFO
}
contractHashes.takeIf { it.isEmpty() }?.apply { ContractManager.logger().error("Contract Hashes is empty, this should not happen!!!") }
protoHashes.takeIf { it.isEmpty() }?.apply { ContractManager.logger().error("Proto Hashes is empty, this should not happen!!!") }
ContractManager.logger().info("ContractHash interface count = ${contractHashes.size}")
ContractManager.logger().info("ProtoHash interface count = ${protoHashes.size}")
heartbeatExecutor.scheduleAtFixedRate(
HeartbeatRunnable(queuers, actualHeartbeatConnections),
1,
1,
TimeUnit.SECONDS
)
heartbeatManagerExecutor.scheduleAtFixedRate(
HeartbeatManagerRunnable(
manager = this,
queuers = queuers,
desired = desiredHeartbeatConnections,
actual = actualHeartbeatConnections,
loggingIterationThreshold = heartbeatLoggingThreshold.seconds,
),
1,
1,
TimeUnit.SECONDS
)
}
private fun getContractHash(clazz: Class): ContractHash {
return contractHashes.find {
it.getClasses()[clazz.name] == true
}.orThrow { IllegalStateException("Unable to find ContractHash instance to match ${clazz.name}, please verify you are running a Provenance bootstrapped JAR.") }
}
private fun getProtoHash(contractHash: ContractHash, clazz: Class<*>): ProtoHash {
return protoHashes.find {
it.getUuid() == contractHash.getUuid() && it.getClasses()[clazz.name] == true
}.orThrow { IllegalStateException("Unable to find ProtoHash instance to match ${clazz.name}, please verify you are running a Provenance bootstrapped JAR.") }
}
private fun newContractProto(contractClazz: Class): Contracts.Contract.Builder =
dehydrateSpec(contractClazz).newContract()
.setDefinition(
ProtoUtil.defSpecBuilderOf(
contractClazz.name,
ProtoUtil.locationBuilderOf(
contractClazz.name,
ProvenanceReference.newBuilder().setHash(getContractHash(contractClazz).getHash()).build()
),
FACT
).build()
)
/**
* Returns a new Contract object that can be configured and executed. This should only be called for creating a Contract
* against a brand new Scope.
*
* @param contractClazz subclass of P8eContract that represents this contract
* @param scopeUuid the scope uuid to set for this contract
* @param executionUuid the execution uuid to set for this contract execution
* @param invokerRole the PartyType to satisfy for this ContractManager's public key
* @param scopeSpecificationName the name of this scope specification
*/
fun newContract(contractClazz: Class, scopeUuid: UUID, executionUuid: UUID? = null, invokerRole: PartyType? = null, scopeSpecificationName: String? = null): Contract {
val scope = Scope.newBuilder()
.setUuid(scopeUuid.toProtoUuidProv())
.setScopeSpecificationName(scopeSpecificationName ?: contractClazz.scopeSpecificationNames().firstOrNull())
.build()
return newContract(contractClazz, scope, executionUuid, invokerRole)
}
/**
* Returns a new Contract object that can be configured and executed. This should be called for creation of all Contract's
* against an existing Scope.
*
* @param contractClazz subclass of P8eContract that represents this contract
* @param scope the Scope to set for this contract
* @param executionUuid the execution uuid to set for this contract execution
* @param invokerRole the PartyType to satisfy for this ContractManager's public key
*/
fun newContract(contractClazz: Class, scope: Scope, executionUuid: UUID? = null, invokerRole: PartyType? = null): Contract {
val env = Envelope.newBuilder()
.setContract(newContractProto(contractClazz).build())
.setExecutionUuid(executionUuid.or { UUID.randomUUID() }.toProtoUuidProv())
.setScope(scope)
.setRef(ProvenanceReference.newBuilder()
.setScopeUuid(scope.uuid)
.setGroupUuid(UUID.randomUUID().toProtoUuidProv())
)
return Contract(
this,
client,
dehydrateSpec(contractClazz),
env.build(),
contractClazz,
contractClassExecutor(contractClazz)
).also { contract ->
invokerRole?.let { contract.satisfyParticipant(it, publicKey) }
}
}
/**
* EXPERIMENTAL
*
* Returns a new Contract object that can be configured and executed. This Contract represents a contract
* based on a previously executed Contract that did not complete successfully. The envelope should contain
* a different execution uuid than was originally associated with the Envelope when it failed execution.
*
* @param contractClazz subclass of P8eContract that represents this contract
* @param envelope the Envelope to set for this contract
*/
fun newContract(contractClazz: Class, envelope: Envelope): Contract {
val contract = envelope.contract
// Validate that the contract is intact enough to build something that can be executed.
require(contract.definition.resourceLocation.classname.isNotEmpty()) {
"Contract is missing [definition.resourceLocation.classname]"
}
val classname = contract.definition.resourceLocation.classname
require(classname == contractClazz.name) {
"Contract classname ${classname} doesn't match cast type of ${contractClazz.name}"
}
return Contract(
this,
client,
dehydrateSpec(contractClazz),
envelope,
contractClazz,
contractClassExecutor(contractClazz)
)
}
/**
* Returns a new Contract object that can be configured and executed. This Contract represents a change in ownership
* of the Scope.
*
* @param contractClazz subclass of P8eContract that represents this contract
* @param scope the Scope to set for this contract
* @param executionUuid the execution uuid to set for this contract execution
* @param invokerRole the PartyType to satisfy for this ContractManager's public key
*/
// TODO: uncomment once re-implemented on new chain
// fun changeScopeOwnership(contractClazz: Class, scope: Scope, executionUuid: UUID? = null, invokerRole: PartyType? = null): Contract {
// val contractProto = newContractProto(contractClazz)
// .setType(Contracts.ContractType.CHANGE_SCOPE)
// .build()
// val env = Envelope.newBuilder()
// .setContract(contractProto)
// .setExecutionUuid(executionUuid.or { UUID.randomUUID() }.toProtoUuidProv())
// .setScope(scope)
// .setRef(ProvenanceReference.newBuilder().setScopeUuid(scope.uuid).setGroupUuid(randomProtoUuidProv()).build())
// .build()
//
// return Contract(
// this,
// client,
// dehydrateSpec(contractClazz),
// env,
// contractClazz,
// contractClassExecutor(contractClazz)
// ).also { contract ->
// invokerRole?.let { contract.satisfyParticipant(it, publicKey) }
// }
// }
/**
* Returns an existing Contract object that can be configured and executed.
*
* @param contractClazz subclass of P8eContract that represents the original contract
* @param executionUuid the execution id of the original contract
* @param isFragment flag identifying whether this contract is to be a response to a previous execution request (true) or a new execution (false)
*/
fun loadContract(
contractClazz: Class,
executionUuid: UUID,
isFragment: Boolean
): Contract {
return Contract(
this,
client,
dehydrateSpec(contractClazz),
client.getContract(executionUuid),
contractClazz,
contractClassExecutor(contractClazz),
isFragment
)
}
/**
* Returns a new Contract object that can be configured and executed. This Contract represents a contract
* based on a previously executed Contract that completed successfully. The envelope will contain
* a different execution uuid than was originally associated with the Envelope when it completed execution.
*
* @param contractClazz subclass of P8eContract that represents the original contract
* @param executionUuid the execution id of the original contract
*/
fun loadContract(
contractClazz: Class,
executionUuid: UUID
): Contract {
return loadContract(
contractClazz,
executionUuid,
false
).also {
it.newExecution()
}
}
fun dehydrateSpec(clazz: Class): ContractSpec {
val contractHash = getContractHash(clazz)
val protoHash = clazz.methods
.find { it.returnType != null && Message::class.java.isAssignableFrom(it.returnType) }
?.returnType
?.let { getProtoHash(contractHash, it) }
.orThrow {
IllegalStateException("Unable to find hash for proto JAR for return types on ${clazz.name}")
}
val contractRef = ProvenanceReference.newBuilder().setHash(contractHash.getHash()).build()
val protoRef = ProvenanceReference.newBuilder().setHash(protoHash.getHash()).build()
return ContractSpecMapper.dehydrateSpec(clazz.kotlin, contractRef, protoRef)
}
fun status(contract: Contract): Status {
return status(contract.envelope.executionUuid.toUuidProv())
}
fun status(executionUuid: UUID): Status {
return client.getContract(executionUuid)
.status
}
fun cancel(contract: Contract, message: String = "") {
cancel(contract.envelope.executionUuid.toUuidProv(), message)
}
fun cancel(executionUuid: UUID, message: String = "") {
client.cancel(executionUuid, message)
}
fun reject(contract: Contract, message: String = "") {
reject(contract.envelope.executionUuid.toUuidProv(), message)
}
fun reject(executionUuid: UUID, message: String = "") {
client.reject(executionUuid, message)
}
fun newEventBuilder(className: String, publicKey: PublicKey): EnvelopeEvent.Builder {
return EnvelopeEvent.newBuilder()
.setClassname(className)
.setPublicKey(
PK.SigningAndEncryptionPublicKeys.newBuilder()
.setSigningPublicKey(publicKey.toPublicKeyProto())
)
}
private fun Class.toProto() = Affiliate.AffiliateContractWhitelist.newBuilder().setClassname(name).build()
class WatchBuilder(
private val publicKey: PublicKey,
private val clazz: Class,
private val contractManager: ContractManager
) {
private var requestHandler: ContractEventHandler = { contract: Contract ->
logger().info("Received contract request on public key ${publicKey.toHex()} for class ${contract.contractClazz.name} with Execution UUID: ${contract.envelope.executionUuid.value}")
true
}.toContractEventHandler()
private var stepCompletionHandler: ContractEventHandler = { contract: Contract ->
logger().info("Received contract response on public key ${publicKey.toHex()} for class ${contract.contractClazz.name} with Scope UUID: ${contract.getScopeUuid()}")
true
}.toContractEventHandler()
private var errorHandler: ContractErrorHandler = { contractError: ContractError ->
logger().error("Received contract error on public key ${publicKey.toHex()} for execution ${contractError.error.executionUuid.value} group ${contractError.error.groupUuid.value}\n${contractError.error.message}")
true
}.toContractErrorHandler()
private var disconnectHandler: DisconnectHandler = { reconnectHandler: ReconnectHandler ->
logger().info("Received disconnect for ${getInfo()}. This handler is deprecated.")
}.toDisconnectHandler()
fun request(requestHandler: ContractEventHandler): WatchBuilder {
this.requestHandler = requestHandler
return this
}
fun request(requestHandler: (Contract) -> Boolean): WatchBuilder {
this.requestHandler = requestHandler.toContractEventHandler()
return this
}
fun stepCompletion(stepCompletionHandler: ContractEventHandler): WatchBuilder {
this.stepCompletionHandler = stepCompletionHandler
return this
}
fun stepCompletion(stepCompletionHandler: (Contract) -> Boolean): WatchBuilder {
this.stepCompletionHandler = stepCompletionHandler.toContractEventHandler()
return this
}
fun error(errorHandler: ContractErrorHandler): WatchBuilder {
this.errorHandler = errorHandler
return this
}
fun error(errorHandler: (ContractError) -> Boolean): WatchBuilder {
this.errorHandler = errorHandler.toContractErrorHandler()
return this
}
@Deprecated("This function is currently a noop.")
fun disconnect(disconnectHandler: DisconnectHandler): WatchBuilder {
// this.disconnectHandler = disconnectHandler
return this
}
@Deprecated("This function is currently a noop.")
fun disconnect(disconnectHandler: (ReconnectHandler) -> Unit): WatchBuilder {
// this.disconnectHandler = disconnectHandler.toDisconnectHandler()
return this
}
fun executeRequests(): WatchBuilder {
this.requestHandler = { contract: Contract ->
logger().info("Received contract request on public key ${publicKey.toHex()} for class ${contract.contractClazz.name}")
if (!contract.isCompleted()) {
contract.execute().isRight()
} else {
true
}
}.toContractEventHandler()
return this
}
fun watch() {
contractManager.watch(
clazz,
requestHandler,
stepCompletionHandler,
errorHandler,
DisconnectHandlerWrapper(
disconnectHandler,
this
)
)
}
fun getInfo(): String {
return "Public Key [${publicKey.toHex()}] Contract Class [${clazz.name}]"
}
}
fun watchBuilder(clazz: Class): WatchBuilder {
return WatchBuilder(publicKey, clazz, this)
}
fun unwatch(clazz: Class) {
desiredHeartbeatConnections.remove(HeartbeatConnectionKey(publicKey, clazz))
handlers.remove(clazz)
}
private fun watch(
clazz: Class,
requestHandler: ContractEventHandler,
stepCompletionHandler: ContractEventHandler,
errorHandler: ContractErrorHandler,
disconnectHandler: DisconnectHandlerWrapper
) = synchronized(watchLock) {
if (queuers.containsKey(clazz) || handlers.containsKey(clazz)) {
throw IllegalStateException("Unable to watch for class ${clazz.name} more than once.")
}
client.whitelistClass(clazz.toProto())
handlers.computeIfAbsent(clazz) {
ContractHandlers(requestHandler, stepCompletionHandler, errorHandler)
}
desiredHeartbeatConnections[HeartbeatConnectionKey(publicKey, clazz)] = { EnvelopeEventObserver(clazz, this::event) }
// Notify other executions that the watch has executed.
watchLock.notify()
}
protected fun event(
clazz: Class,
event: EnvelopeEvent
) {
if (event.action == EnvelopeEvent.Action.HEARTBEAT) {
return
}
val classHandlers = handlers[clazz]
.orThrow { IllegalStateException("Handlers not registered for ${clazz.name}") }
val response = when (event.event) {
EventType.ENVELOPE_ACCEPTED -> false
EventType.ENVELOPE_RESPONSE -> try {
classHandlers.stepCompletionHandler.cast().handle(constructContract(clazz, event))
} catch (t: Throwable) {
logger().error("Error during step completion handler: ", t)
throw t
}
EventType.ENVELOPE_REQUEST -> try {
classHandlers.requestHandler.cast().handle(constructContract(clazz, event))
} catch (t: Throwable) {
logger().error("Error during request handler: ", t)
throw t
}
EventType.ENVELOPE_ERROR -> try {
val contractError = ContractError(
contractClazz = clazz,
event = event,
error = event.error,
)
classHandlers.errorHandler.cast().handle(contractError)
} catch (t: Throwable) {
logger().error("Error during error handler: ", t)
throw t
}
else -> throw IllegalStateException("Unknown event type ${event.event.name}")
}
if (response) {
queuers[clazz]?.queue(event.toBuilder().setAction(EnvelopeEvent.Action.ACK).build())
}
}
fun ackProcessedEvent(contract: Contract) {
contract.constructedFromEvent
.takeIf { it != null }
?.let { ackProcessedEvent(contract.contractClazz, it) }
}
fun ackProcessedEvent(contractError: ContractError) {
ackProcessedEvent(contractError.contractClazz, contractError.event)
}
private fun ackProcessedEvent(contractClazz: Class, event: EnvelopeEvent) {
if (event.action == EnvelopeEvent.Action.HEARTBEAT) {
return
}
queuers[contractClazz]?.queue(event.toBuilder().setAction(EnvelopeEvent.Action.ACK).build())
}
@Suppress("UNCHECKED_CAST")
private fun ContractEventHandler.cast(): ContractEventHandler {
return this as ContractEventHandler
}
@Suppress("UNCHECKED_CAST")
private fun ContractErrorHandler.cast(): ContractErrorHandler {
return this as ContractErrorHandler
}
private fun constructContract(
clazz: Class,
event: EnvelopeEvent
): Contract {
return Contract(
this,
client,
dehydrateSpec(clazz),
event.envelope,
clazz,
contractClassExecutor(clazz),
when (event.event) {
EventType.ENVELOPE_REQUEST -> true
EventType.ENVELOPE_RESPONSE -> false
EventType.ENVELOPE_ACCEPTED -> false
else -> throw IllegalStateException("Unable to handle event of type ${event.event.name} as REQUEST/RESPONSE")
},
event
)
}
private fun contractClassExecutor(clazz: Class): (request: EnvelopeEvent) -> Either> = { request ->
try {
client.execute(request)
.let { response ->
when (response.event) {
EventType.ENVELOPE_EXECUTION_ERROR, EventType.ENVELOPE_ERROR -> Either.Left(response.error.p8eError())
EventType.ENVELOPE_ACCEPTED,
EventType.ENVELOPE_MAILBOX_OUTBOUND,
EventType.ENVELOPE_REQUEST,
EventType.ENVELOPE_RESPONSE -> Either.Right(constructContract(clazz, response))
EventType.UNRECOGNIZED, EventType.UNUSED_TYPE, null -> throw IllegalStateException("Invalid EventType of ${response.event}")
}
}
} catch (t: Throwable) {
Either.Left(t.p8eError())
}
}
// TODO - determine what we're doing with proto loading and retrieval since contract engine
// requires the jar anyways before a contract spec can run so just a spec proto is insufficient.
inline fun loadProto(uri: String): T = client.loadProto(uri, T::class.java)
fun loadProto(uri: String, className: String) = client.loadProto(uri, Class.forName(className) as Class)
fun saveProto(
msg: Message,
executionUuid: UUID? = null,
audience: Set = setOf()
): Location = client.saveProto(
msg,
executionUuid,
audience = audience
)
inline fun getFactHistory(
scopeUuid: UUID,
factName: String,
startWindow: OffsetDateTime = Timestamps.MIN_VALUE.toOffsetDateTimeProv(),
endWindow: OffsetDateTime = Timestamps.MAX_VALUE.toOffsetDateTimeProv()
): List> {
return getFactHistory(
scopeUuid,
factName,
T::class.java,
startWindow,
endWindow
)
}
/**
* Retrieve a list of facts for a given fact name sorted by when it was created desc.
* Optional configuration to allow fetching fact history within a given window of time.
*/
fun getFactHistory(
scopeUuid: UUID,
factName: String,
clazz: Class,
startWindow: OffsetDateTime = Timestamps.MIN_VALUE.toOffsetDateTimeProv(),
endWindow: OffsetDateTime = Timestamps.MAX_VALUE.toOffsetDateTimeProv()
): List> {
return client.getFactHistory(
scopeUuid,
factName,
clazz.name,
startWindow,
endWindow
).entriesList
.map { entry ->
val updatedTimestamp = entry.factAuditFields
.updatedDate
.takeIf { it != Timestamp.getDefaultInstance() }
?: entry.factAuditFields.createdDate
val fact = client.loadProto(
entry.factBytes.toByteArray(),
clazz.name
).let { message ->
when {
clazz.isAssignableFrom(message.javaClass) -> clazz.cast(message)
else -> throw IllegalStateException("Unable to cast ${message.javaClass.name} as ${clazz.name}")
}
}
FactSnapshot(
entry.executor,
entry.partiesList,
entry.contractJarHash,
entry.contractClassname,
entry.functionName,
entry.resultName,
entry.resultHash,
fact,
updatedTimestamp.toOffsetDateTimeProv(),
entry.blockNumber,
entry.blockTransactionIndex
)
}.sortedByDescending { it.updated }
}
/**
* Load a spec from POS.
*
* @return [ContractSpec] (proto).
*/
fun loadContract(uri: String) = loadProto(uri)
fun hydrate(scopeUuid: UUID, clazz: Class): T {
val scope = indexClient.findLatestScopeByUuid(scopeUuid)
.orThrow { IllegalStateException("Unable to find scope with uuid $scopeUuid") }
.scope
return factPojoHydrator.hydrate(
scope,
clazz
)
}
override fun close() {
heartbeatExecutor.shutdown()
heartbeatManagerExecutor.shutdown()
queuers.forEach { (className, _) ->
unwatch(className)
actualHeartbeatConnections.remove(HeartbeatConnectionKey(publicKey, className))
queuers.remove(className)?.close(CompleteState)
}
channel?.shutdown()
}
}
fun ((Contract) -> Boolean).toContractEventHandler(): ContractEventHandler {
return object: ContractEventHandler {
override fun handle(contract: Contract): Boolean {
return invoke(contract)
}
}
}
fun ((ContractError) -> Boolean).toContractErrorHandler(): ContractErrorHandler {
return object: ContractErrorHandler {
override fun handle(contractError: ContractError): Boolean {
return invoke(contractError)
}
}
}
fun ((ReconnectHandler) -> Unit).toDisconnectHandler(): DisconnectHandler {
return object: DisconnectHandler {
override fun handle(reconnectHandler: ReconnectHandler) {
return invoke(reconnectHandler)
}
}
}