All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.p8e.proxy.ContractProxy.kt Maven / Gradle / Ivy

Go to download

A collection of services and libraries that iteract and run Provenance Java based contracts.

There is a newer version: 0.9.0-grpc
Show newest version
package io.p8e.proxy

import com.google.protobuf.Message
import io.p8e.ContractManager
import io.p8e.annotations.Prerequisite
import io.p8e.annotations.Function
import io.p8e.annotations.Fact
import io.p8e.annotations.Input
import io.p8e.proto.Common.Location
import io.p8e.proto.Contracts.Contract
import io.p8e.spec.P8eContract
import io.p8e.util.orThrow
import io.p8e.util.orThrowContractDefinition
import io.p8e.util.orThrowNotFound
import javassist.util.proxy.MethodFilter
import javassist.util.proxy.MethodHandler
import javassist.util.proxy.ProxyFactory
import java.lang.reflect.Constructor
import java.lang.reflect.Method
import java.lang.reflect.Parameter
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KFunction

enum class FactType {
    PROPOSED,
    FACT
}
data class MethodReflect(val prerequisite: Prerequisite?, val function: Function?,
                         val parameters: List, val inputs: Map) {
    fun hasPrerequisite() = prerequisite != null
    fun hasFunction() = function != null
}

class ContractProxy(
    private val contractManager: ContractManager,
    private var contract: Contract
): MethodFilter, MethodHandler {
    private val methodResultCache = ConcurrentHashMap()
    private val methodReflectCache = ConcurrentHashMap()
    private val handlers = mapOf>(
//        "execute" to ::executeHandler,
        "getContract" to ::getContract
    )

    companion object : P8EProxyProvider {
        override fun  newProxy(
            contractManager: ContractManager,
            contract: Contract,
            contractClass: Class
        ): T {
            val contractProxy = ContractProxy(contractManager, contract)

            val factory = ProxyFactory()
            factory.superclass = contractClass
            factory.setFilter(contractProxy)

            val constructor = contractClass.declaredConstructors.first()
            val args = getConstructorParameters(
                contractManager,
                constructor,
                contract
            )

            val result = factory.create(
                args.map { it.javaClass }.toTypedArray(),
                args.toTypedArray(),
                contractProxy
            )

            return contractClass.cast(result)
        }

        private fun getConstructorParameters(
            contractManager: ContractManager,
            constructor: Constructor<*>,
            contract: Contract
        ): List {
            val paramNamesToType = constructor.parameters
                .map { param ->
                    param.getAnnotation(Fact::class.java)
                        .orThrowContractDefinition("All constructor parameters for ${constructor.declaringClass} must be annotated with ${Fact::class.java.name}")
                        .name to param.type
                }

            val inputs = contract.inputsList
                .filter { it.dataLocation != Location.getDefaultInstance() }
                .groupBy { it.name }
                .filter { (name, facts) ->
                    paramNamesToType.any { (paramName, clazz) ->
                        name == paramName && clazz.name == facts.firstOrNull()?.dataLocation?.classname
                    }
                }
                .mapValues {(_, facts) ->
                    facts.map { fact ->
                        contractManager.loadProto(fact.dataLocation.ref.hash, fact.dataLocation.classname)
                    }
                }

            val listInputs = inputs.filter { it.value.size > 1 }
            val singleInputs = inputs.filter { it.value.size == 1 }
                .mapValues { it.value.first() }

            return paramNamesToType.mapNotNull { (name, _) ->
                listInputs[name] ?: singleInputs[name]
            }
        }
    }

    fun cache(m: Method): MethodReflect =
        methodReflectCache.getOrPut(m) {
            val prerequisite = m.getAnnotation(Prerequisite::class.java)
            val function = m.getAnnotation(Function::class.java)

            val parameters = m.parameters.map { it }

            val inputs = mutableMapOf().apply {
                putAll(m.parameters.filter { it.isAnnotationPresent(Fact::class.java) }.map { it.name to FactType.FACT }.toMap())
                putAll(m.parameters.filter { it.isAnnotationPresent(Input::class.java) }.map { it.name to FactType.PROPOSED }.toMap())
            }

            MethodReflect(prerequisite, function, parameters, inputs)
        }

    override fun isHandled(m: Method?) =  m != null &&
        (handlers.containsKey(m.name) || cache(m).hasFunction() || cache(m).hasPrerequisite())

    fun executeHandler(self: Any?,
                       thisMethod: Method,
                       proceed: Method?,
                       args: Array?) : Any? {
        self as P8eContract
        return CompletableFuture()
    }

    fun getContract(self: Any?,
                    thisMethod: Method,
                    proceed: Method?,
                    args: Array?) : Any? {
        self as P8eContract
        return this.contract
    }

    fun handlePrerequisite(self: Any?,
                           thisMethod: Method,
                           proceed: Method?,
                           args: Array?) : Any? {
        self as P8eContract
        return proceed!!.invoke(self, args)
    }

    fun handleFunction(self: Any?,
                       thisMethod: Method,
                       proceed: Method?,
                       args: Array?) : Any? {
        val consideration = contract.considerationsList
            .filter { it.considerationName == thisMethod.name }
            .orThrowNotFound("Unable to find function for ${thisMethod.name}")
            .first()

        // Check to see if the current contract has an output result available.
        val result = consideration
            .takeIf { it.hasResult() }
            ?.let { it.result }
            ?.let { getResult(it.output.name, it.output.hash, it.output.classname) }
            ?: throw IllegalStateException("Contract does not yet have a result for function ${consideration.considerationName}")

        // TODO verify that the input vars match the facts used as inputs in the previous run of the contract. If not error.
        return result
    }

    override fun invoke(
        self: Any?,
        thisMethod: Method?,
        proceed: Method?,
        args: Array?
    ): Any? {
        if (self == null || thisMethod == null) {
            return null
        }

        // Call the handler if it's available.
        val handler = handlers[thisMethod.name]
        if (handler != null)
            return handler.call(self, thisMethod, proceed, args)

        val c = cache(thisMethod)
        if (c.hasPrerequisite())
            return handlePrerequisite(self, thisMethod, proceed, args)
        if (c.hasFunction())
            return handleFunction(self, thisMethod, proceed, args)

        return proceed!!.invoke(self, args)
    }

    private fun getResult(
        name: String,
        hash: String,
        classname: String
    ): Message {
        return methodResultCache.computeIfAbsent(name) {
            contractManager.loadProto(hash, classname)
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy