[CORDA-760]: Propagate invocation context across the codebase. (#2016)

This commit is contained in:
Michele Sollecito
2017-11-15 14:58:43 +00:00
committed by GitHub
parent f0a5ea96e7
commit 92c8861802
70 changed files with 1177 additions and 313 deletions

View File

@ -1,15 +1,24 @@
package net.corda.nodeapi
import net.corda.core.context.Actor
import net.corda.core.context.AuthServiceId
import net.corda.core.context.Trace
import net.corda.core.context.Trace.InvocationId
import net.corda.core.context.Trace.SessionId
import net.corda.core.identity.CordaX500Name
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.Id
import net.corda.core.utilities.Try
import org.apache.activemq.artemis.api.core.ActiveMQBuffer
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.*
import org.apache.activemq.artemis.api.core.management.CoreNotificationType
import org.apache.activemq.artemis.api.core.management.ManagementHelper
import org.apache.activemq.artemis.reader.MessageUtil
import rx.Notification
import java.time.Instant
import java.util.*
// The RPC protocol:
@ -51,10 +60,6 @@ import java.util.*
* Constants and data types used by the RPC API.
*/
object RPCApi {
private val TAG_FIELD_NAME = "tag"
private val RPC_ID_FIELD_NAME = "rpc-id"
private val OBSERVABLE_ID_FIELD_NAME = "observable-id"
private val METHOD_NAME_FIELD_NAME = "method-name"
/** Name of the Artemis queue on which the server receives RPC requests (as [ClientToServer.RpcRequest]). */
const val RPC_SERVER_QUEUE_NAME = "rpc.server"
@ -65,6 +70,7 @@ object RPCApi {
const val RPC_CLIENT_QUEUE_NAME_PREFIX = "rpc.client"
const val RPC_CLIENT_BINDING_REMOVALS = "rpc.clientqueueremovals"
const val RPC_CLIENT_BINDING_ADDITIONS = "rpc.clientqueueadditions"
const val RPC_TARGET_LEGAL_IDENTITY = "rpc-target-legal-identity"
val RPC_CLIENT_BINDING_REMOVAL_FILTER_EXPRESSION =
"${ManagementHelper.HDR_NOTIFICATION_TYPE} = '${CoreNotificationType.BINDING_REMOVED.name}' AND " +
@ -73,9 +79,6 @@ object RPCApi {
"${ManagementHelper.HDR_NOTIFICATION_TYPE} = '${CoreNotificationType.BINDING_ADDED.name}' AND " +
"${ManagementHelper.HDR_ROUTING_NAME} LIKE '$RPC_CLIENT_QUEUE_NAME_PREFIX.%'"
data class RpcRequestId(val toLong: Long)
data class ObservableId(val toLong: Long)
object RpcRequestOrObservableIdKey
private fun ClientMessage.getBodyAsByteArray(): ByteArray {
@ -101,28 +104,35 @@ object RPCApi {
*/
data class RpcRequest(
val clientAddress: SimpleString,
val id: RpcRequestId,
val methodName: String,
val serialisedArguments: ByteArray
val serialisedArguments: ByteArray,
val replyId: InvocationId,
val sessionId: SessionId,
val externalTrace: Trace? = null,
val impersonatedActor: Actor? = null
) : ClientToServer() {
fun writeToClientMessage(message: ClientMessage) {
MessageUtil.setJMSReplyTo(message, clientAddress)
message.putIntProperty(TAG_FIELD_NAME, Tag.RPC_REQUEST.ordinal)
message.putLongProperty(RPC_ID_FIELD_NAME, id.toLong)
replyId.mapTo(message)
sessionId.mapTo(message)
externalTrace?.mapToExternal(message)
impersonatedActor?.mapToImpersonated(message)
message.putStringProperty(METHOD_NAME_FIELD_NAME, methodName)
message.bodyBuffer.writeBytes(serialisedArguments)
}
}
data class ObservablesClosed(
val ids: List<ObservableId>
) : ClientToServer() {
data class ObservablesClosed(val ids: List<InvocationId>) : ClientToServer() {
fun writeToClientMessage(message: ClientMessage) {
message.putIntProperty(TAG_FIELD_NAME, Tag.OBSERVABLES_CLOSED.ordinal)
val buffer = message.bodyBuffer
buffer.writeInt(ids.size)
ids.forEach {
buffer.writeLong(it.toLong)
buffer.writeInvocationId(it)
}
}
}
@ -133,16 +143,19 @@ object RPCApi {
return when (tag) {
RPCApi.ClientToServer.Tag.RPC_REQUEST -> RpcRequest(
clientAddress = MessageUtil.getJMSReplyTo(message),
id = RpcRequestId(message.getLongProperty(RPC_ID_FIELD_NAME)),
methodName = message.getStringProperty(METHOD_NAME_FIELD_NAME),
serialisedArguments = message.getBodyAsByteArray()
serialisedArguments = message.getBodyAsByteArray(),
replyId = message.replyId(),
sessionId = message.sessionId(),
externalTrace = message.externalTrace(),
impersonatedActor = message.impersonatedActor()
)
RPCApi.ClientToServer.Tag.OBSERVABLES_CLOSED -> {
val ids = ArrayList<ObservableId>()
val ids = ArrayList<InvocationId>()
val buffer = message.bodyBuffer
val numberOfIds = buffer.readInt()
for (i in 1..numberOfIds) {
ids.add(ObservableId(buffer.readLong()))
ids.add(buffer.readInvocationId())
}
ObservablesClosed(ids)
}
@ -164,23 +177,23 @@ object RPCApi {
/** Reply in response to an [ClientToServer.RpcRequest]. */
data class RpcReply(
val id: RpcRequestId,
val id: InvocationId,
val result: Try<Any?>
) : ServerToClient() {
override fun writeToClientMessage(context: SerializationContext, message: ClientMessage) {
message.putIntProperty(TAG_FIELD_NAME, Tag.RPC_REPLY.ordinal)
message.putLongProperty(RPC_ID_FIELD_NAME, id.toLong)
id.mapTo(message, RPC_ID_FIELD_NAME, RPC_ID_TIMESTAMP_FIELD_NAME)
message.bodyBuffer.writeBytes(result.safeSerialize(context) { Try.Failure<Any>(it) }.bytes)
}
}
data class Observation(
val id: ObservableId,
val id: InvocationId,
val content: Notification<*>
) : ServerToClient() {
override fun writeToClientMessage(context: SerializationContext, message: ClientMessage) {
message.putIntProperty(TAG_FIELD_NAME, Tag.OBSERVATION.ordinal)
message.putLongProperty(OBSERVABLE_ID_FIELD_NAME, id.toLong)
id.mapTo(message, OBSERVABLE_ID_FIELD_NAME, OBSERVABLE_ID_TIMESTAMP_FIELD_NAME)
message.bodyBuffer.writeBytes(content.safeSerialize(context) { Notification.createOnError<Void?>(it) }.bytes)
}
}
@ -196,20 +209,15 @@ object RPCApi {
val tag = Tag.values()[message.getIntProperty(TAG_FIELD_NAME)]
return when (tag) {
RPCApi.ServerToClient.Tag.RPC_REPLY -> {
val id = RpcRequestId(message.getLongProperty(RPC_ID_FIELD_NAME))
val poolWithIdContext = context.withProperty(RpcRequestOrObservableIdKey, id.toLong)
RpcReply(
id = id,
result = message.getBodyAsByteArray().deserialize(context = poolWithIdContext)
)
val id = message.invocationId(RPC_ID_FIELD_NAME, RPC_ID_TIMESTAMP_FIELD_NAME) ?: throw IllegalStateException("Cannot parse invocation id from client message.")
val poolWithIdContext = context.withProperty(RpcRequestOrObservableIdKey, id)
RpcReply(id, message.getBodyAsByteArray().deserialize(context = poolWithIdContext))
}
RPCApi.ServerToClient.Tag.OBSERVATION -> {
val id = ObservableId(message.getLongProperty(OBSERVABLE_ID_FIELD_NAME))
val poolWithIdContext = context.withProperty(RpcRequestOrObservableIdKey, id.toLong)
Observation(
id = id,
content = message.getBodyAsByteArray().deserialize(context = poolWithIdContext)
)
val observableId = message.invocationId(OBSERVABLE_ID_FIELD_NAME, OBSERVABLE_ID_TIMESTAMP_FIELD_NAME) ?: throw IllegalStateException("Cannot parse invocation id from client message.")
val poolWithIdContext = context.withProperty(RpcRequestOrObservableIdKey, observableId)
val payload = message.getBodyAsByteArray().deserialize<Notification<*>>(context = poolWithIdContext)
Observation(observableId, payload)
}
}
}
@ -228,3 +236,108 @@ data class ArtemisConsumer(
val session: ClientSession,
val consumer: ClientConsumer
)
private val TAG_FIELD_NAME = "tag"
private val RPC_ID_FIELD_NAME = "rpc-id"
private val RPC_ID_TIMESTAMP_FIELD_NAME = "rpc-id-timestamp"
private val RPC_SESSION_ID_FIELD_NAME = "rpc-session-id"
private val RPC_SESSION_ID_TIMESTAMP_FIELD_NAME = "rpc-session-id-timestamp"
private val RPC_EXTERNAL_ID_FIELD_NAME = "rpc-external-id"
private val RPC_EXTERNAL_ID_TIMESTAMP_FIELD_NAME = "rpc-external-id-timestamp"
private val RPC_EXTERNAL_SESSION_ID_FIELD_NAME = "rpc-external-session-id"
private val RPC_EXTERNAL_SESSION_ID_TIMESTAMP_FIELD_NAME = "rpc-external-session-id-timestamp"
private val RPC_IMPERSONATED_ACTOR_ID = "rpc-impersonated-actor-id"
private val RPC_IMPERSONATED_ACTOR_STORE_ID = "rpc-impersonated-actor-store-id"
private val RPC_IMPERSONATED_ACTOR_OWNING_LEGAL_IDENTITY = "rpc-impersonated-actor-owningLegalIdentity"
private val OBSERVABLE_ID_FIELD_NAME = "observable-id"
private val OBSERVABLE_ID_TIMESTAMP_FIELD_NAME = "observable-id-timestamp"
private val METHOD_NAME_FIELD_NAME = "method-name"
fun ClientMessage.replyId(): InvocationId {
return invocationId(RPC_ID_FIELD_NAME, RPC_ID_TIMESTAMP_FIELD_NAME) ?: throw IllegalStateException("Cannot extract reply id from client message.")
}
fun ClientMessage.sessionId(): SessionId {
return sessionId(RPC_SESSION_ID_FIELD_NAME, RPC_SESSION_ID_TIMESTAMP_FIELD_NAME) ?: throw IllegalStateException("Cannot extract the session id from client message.")
}
fun ClientMessage.externalTrace(): Trace? {
val invocationId = invocationId(RPC_EXTERNAL_ID_FIELD_NAME, RPC_EXTERNAL_ID_TIMESTAMP_FIELD_NAME)
val sessionId = sessionId(RPC_EXTERNAL_SESSION_ID_FIELD_NAME, RPC_EXTERNAL_SESSION_ID_TIMESTAMP_FIELD_NAME)
return when {
invocationId == null || sessionId == null -> null
else -> Trace(invocationId, sessionId)
}
}
fun ClientMessage.impersonatedActor(): Actor? {
return getStringProperty(RPC_IMPERSONATED_ACTOR_ID)?.let {
val impersonatedStoreId = getStringProperty(RPC_IMPERSONATED_ACTOR_STORE_ID)
val impersonatingOwningLegalIdentity = getStringProperty(RPC_IMPERSONATED_ACTOR_OWNING_LEGAL_IDENTITY)
if (impersonatedStoreId == null || impersonatingOwningLegalIdentity == null) {
throw IllegalStateException("Cannot extract impersonated actor from client message.")
}
Actor(Actor.Id(it), AuthServiceId(impersonatedStoreId), CordaX500Name.parse(impersonatingOwningLegalIdentity))
}
}
private fun Id<String>.mapTo(message: ClientMessage, valueProperty: String, timestampProperty: String) {
message.putStringProperty(valueProperty, value)
message.putLongProperty(timestampProperty, timestamp.toEpochMilli())
}
private fun ActiveMQBuffer.writeInvocationId(invocationId: InvocationId) {
this.writeString(invocationId.value)
this.writeLong(invocationId.timestamp.toEpochMilli())
}
private fun ActiveMQBuffer.readInvocationId() : InvocationId {
val value = this.readString()
val timestamp = this.readLong()
return InvocationId(value, Instant.ofEpochMilli(timestamp))
}
private fun InvocationId.mapTo(message: ClientMessage) = mapTo(message, RPC_ID_FIELD_NAME, RPC_ID_TIMESTAMP_FIELD_NAME)
private fun SessionId.mapTo(message: ClientMessage) = mapTo(message, RPC_SESSION_ID_FIELD_NAME, RPC_SESSION_ID_TIMESTAMP_FIELD_NAME)
private fun Trace.mapToExternal(message: ClientMessage) = mapTo(message, RPC_EXTERNAL_ID_FIELD_NAME, RPC_EXTERNAL_ID_TIMESTAMP_FIELD_NAME, RPC_EXTERNAL_SESSION_ID_FIELD_NAME, RPC_EXTERNAL_SESSION_ID_TIMESTAMP_FIELD_NAME)
private fun Actor.mapToImpersonated(message: ClientMessage) {
message.putStringProperty(RPC_IMPERSONATED_ACTOR_ID, this.id.value)
message.putStringProperty(RPC_IMPERSONATED_ACTOR_STORE_ID, this.serviceId.value)
message.putStringProperty(RPC_IMPERSONATED_ACTOR_OWNING_LEGAL_IDENTITY, this.owningLegalIdentity.toString())
}
private fun Trace.mapTo(message: ClientMessage, valueProperty: String, timestampProperty: String, sessionValueProperty: String, sessionTimestampProperty: String) = apply {
invocationId.apply {
message.putStringProperty(valueProperty, value)
message.putLongProperty(timestampProperty, timestamp.toEpochMilli())
}
sessionId.apply {
message.putStringProperty(sessionValueProperty, value)
message.putLongProperty(sessionTimestampProperty, timestamp.toEpochMilli())
}
}
private fun ClientMessage.invocationId(valueProperty: String, timestampProperty: String): InvocationId? = id(valueProperty, timestampProperty, ::InvocationId)
private fun ClientMessage.sessionId(valueProperty: String, timestampProperty: String): SessionId? = id(valueProperty, timestampProperty, ::SessionId)
private fun <ID : Id<*>> ClientMessage.id(valueProperty: String, timestampProperty: String, construct: (value: String, timestamp: Instant) -> ID): ID? {
// returning null because getLongProperty throws trying to convert null to long
val idRaw = this.getStringProperty(valueProperty) ?: return null
val timestampRaw = this.getLongProperty(timestampProperty)
return construct(idRaw, Instant.ofEpochMilli(timestampRaw))
}