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

commonMain.dk.cachet.carp.common.infrastructure.services.ApplicationServiceLogger.kt Maven / Gradle / Ivy

Go to download

Helper classes and base types relied upon by all subsystems. This library does not contain any domain logic.

The newest version!
package dk.cachet.carp.common.infrastructure.services

import dk.cachet.carp.common.application.services.ApplicationService
import dk.cachet.carp.common.application.services.IntegrationEvent
import dk.cachet.carp.common.infrastructure.reflect.AccessInternals
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.JsonClassDiscriminator


/**
 * Access [loggedRequests] of an [ApplicationService].
 */
class ApplicationServiceLogger<
    TService : ApplicationService,
    TEvent : IntegrationEvent
>
{
    private val _loggedRequests: MutableList> = mutableListOf()
    val loggedRequests: List>
        get() = _loggedRequests.toList()

    fun addLog( loggedRequest: LoggedRequest ) = _loggedRequests.add( loggedRequest )

    /**
     * Determines whether the given [request] is present in [loggedRequests].
     */
    fun wasCalled( request: ApplicationServiceRequest ): Boolean =
        _loggedRequests.map { it.request }.contains( request )

    /**
     * Clear the current [loggedRequests].
     */
    fun clear() = _loggedRequests.clear()
}


/**
 * An intercepted [request] and response to an application service [TService].
 */
sealed interface LoggedRequest>
{
    val request: ApplicationServiceRequest
    val precedingEvents: List>
    val publishedEvents: List>

    /**
     * The intercepted [request] succeeded and returned [response].
     */
    data class Succeeded>(
        override val request: ApplicationServiceRequest,
        override val precedingEvents: List>,
        override val publishedEvents: List>,
        val response: Any?
    ) : LoggedRequest

    /**
     * The intercepted [request] failed with an exception of [exceptionType].
     */
    data class Failed>(
        override val request: ApplicationServiceRequest,
        override val precedingEvents: List>,
        override val publishedEvents: List>,
        val exceptionType: String
    ) : LoggedRequest
}


/**
 * Serializer for [LoggedRequest]s of [TService].
 */
@OptIn( ExperimentalSerializationApi::class, InternalSerializationApi::class )
class LoggedRequestSerializer>(
    /**
     * The request serializer for [TService] which can polymorphically serialize any of its requests.
     */
    requestSerializer: KSerializer>,
    /**
     * A serializer for any of the events that may be received or are published by [TService].
     */
    eventSerializer: KSerializer>
) : KSerializer>
{
    private val eventsSerializer = ListSerializer( eventSerializer )

    @Suppress( "MagicNumber" )
    private val succeededSerializer =
        object : KSerializer>
        {
            private val responseSerialDescriptor = buildClassSerialDescriptor(
                "${LoggedRequestSerializer::class.simpleName!!}\$Response"
            )

            override val descriptor: SerialDescriptor =
                buildClassSerialDescriptor( LoggedRequest.Succeeded::class.simpleName!! )
                {
                    element( LoggedRequest<*>::request.name, requestSerializer.descriptor )
                    element( LoggedRequest<*>::precedingEvents.name, eventsSerializer.descriptor )
                    element( LoggedRequest<*>::publishedEvents.name, eventsSerializer.descriptor )
                    element( LoggedRequest.Succeeded<*>::response.name, responseSerialDescriptor )
                }

            @Suppress( "UNCHECKED_CAST" )
            override fun serialize( encoder: Encoder, value: LoggedRequest.Succeeded<*> )
            {
                val responseSerializer = value.request.getResponseSerializer() as KSerializer
                val anyEventsSerializer = eventsSerializer as KSerializer

                encoder.encodeStructure( descriptor )
                {
                    encodeSerializableElement( descriptor, 0, requestSerializer as KSerializer, value.request )
                    encodeSerializableElement( descriptor, 1, anyEventsSerializer, value.precedingEvents )
                    encodeSerializableElement( descriptor, 2, anyEventsSerializer, value.publishedEvents )
                    encodeSerializableElement( descriptor, 3, responseSerializer, value.response )
                }
            }

            override fun deserialize( decoder: Decoder ): LoggedRequest.Succeeded<*> =
                decoder.decodeStructure( descriptor )
                {
                    var request: ApplicationServiceRequest? = null
                    var precedingEvents: List>? = null
                    var publishedEvents: List>? = null
                    var response: Any? = null

                    var decoding = true
                    while ( decoding )
                    {
                        when ( val index = decodeElementIndex( descriptor ) )
                        {
                            0 -> request = decodeSerializableElement( descriptor, 0, requestSerializer )
                            1 -> precedingEvents = decodeSerializableElement( descriptor, 1, eventsSerializer )
                            2 -> publishedEvents = decodeSerializableElement( descriptor, 2, eventsSerializer )
                            3 ->
                            {
                                val responseSerializer = checkNotNull( request ).getResponseSerializer()
                                response = decodeSerializableElement( descriptor, 3, responseSerializer )
                            }
                            CompositeDecoder.DECODE_DONE -> decoding = false
                            else -> error( "Unexpected index: $index" )
                        }
                    }

                    LoggedRequest.Succeeded(
                        checkNotNull( request ),
                        checkNotNull( precedingEvents ),
                        checkNotNull( publishedEvents ),
                        response
                    )
                }
        }

    @Suppress( "MagicNumber" )
    private val failedSerializer =
        object : KSerializer>
        {
            private val exceptionSerializer = serializer()

            override val descriptor: SerialDescriptor =
                buildClassSerialDescriptor( LoggedRequest.Failed::class.simpleName!! )
                {
                    element( LoggedRequest<*>::request.name, requestSerializer.descriptor )
                    element( LoggedRequest<*>::precedingEvents.name, eventsSerializer.descriptor )
                    element( LoggedRequest<*>::publishedEvents.name, eventsSerializer.descriptor )
                    element( LoggedRequest.Failed<*>::exceptionType.name, exceptionSerializer.descriptor )
                }

            @Suppress( "UNCHECKED_CAST" )
            override fun serialize( encoder: Encoder, value: LoggedRequest.Failed<*> )
            {
                val anyEventsSerializer = eventsSerializer as KSerializer

                encoder.encodeStructure( descriptor )
                {
                    encodeSerializableElement( descriptor, 0, requestSerializer as KSerializer, value.request )
                    encodeSerializableElement( descriptor, 1, anyEventsSerializer, value.precedingEvents )
                    encodeSerializableElement( descriptor, 2, anyEventsSerializer, value.publishedEvents )
                    encodeSerializableElement( descriptor, 3, exceptionSerializer, value.exceptionType )
                }
            }

            override fun deserialize( decoder: Decoder ): LoggedRequest.Failed<*> =
                decoder.decodeStructure( descriptor )
                {
                    var request: ApplicationServiceRequest? = null
                    var precedingEvents: List>? = null
                    var publishedEvents: List>? = null
                    var exceptionType: String? = null

                    var decoding = true
                    while ( decoding )
                    {
                        when ( val index = decodeElementIndex( descriptor ) )
                        {
                            0 -> request = decodeSerializableElement( descriptor, 0, requestSerializer )
                            1 -> precedingEvents = decodeSerializableElement( descriptor, 1, eventsSerializer )
                            2 -> publishedEvents = decodeSerializableElement( descriptor, 2, eventsSerializer )
                            3 -> exceptionType = decodeSerializableElement( descriptor, 3, exceptionSerializer )
                            CompositeDecoder.DECODE_DONE -> decoding = false
                            else -> error( "Unexpected index: $index" )
                        }
                    }

                    LoggedRequest.Failed(
                        checkNotNull( request ),
                        checkNotNull( precedingEvents ),
                        checkNotNull( publishedEvents ),
                        checkNotNull( exceptionType )
                    )
                }
        }


    private val sealedSerializer = SealedClassSerializer(
        LoggedRequest::class.simpleName!!,
        LoggedRequest::class,
        arrayOf( LoggedRequest.Succeeded::class, LoggedRequest.Failed::class ),
        arrayOf( succeededSerializer, failedSerializer )
    ).apply {
        // HACK: Change class discriminator so that it does not depend on JsonConfiguration.
        //   For now the secondary constructor which allows setting annotations is internal; it may become public later.
        AccessInternals.setField( this, "_annotations", listOf( JsonClassDiscriminator("outcome" ) ) )
    }

    override val descriptor: SerialDescriptor = sealedSerializer.descriptor

    override fun serialize( encoder: Encoder, value: LoggedRequest<*> ) =
        encoder.encodeSerializableValue( sealedSerializer, value )

    override fun deserialize( decoder: Decoder ): LoggedRequest<*> =
        decoder.decodeSerializableValue( sealedSerializer )
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy