diff --git a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/context/ContextSerializable.kt b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/context/ContextSerializable.kt deleted file mode 100644 index 1597ebf1..00000000 --- a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/context/ContextSerializable.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.justai.jaicf.channel.jaicp.context - -import com.justai.jaicf.context.BotContext - -internal interface ContextSerializable { - fun serialized(): String - - val service: ContextSerializableService<*> - - fun saveToContext(ctx: BotContext) = ctx.session.put(service.key, serialized()) - - fun removeFromContext(ctx: BotContext) = ctx.session.remove(service.key) -} - -internal interface ContextSerializableService { - val key: String - - fun deserialize(content: String): T - - fun fromContext(ctx: BotContext) = (ctx.session[key] as? String)?.let { deserialize(it) } - - fun cleanup(ctx: BotContext) = ctx.session.remove(key) -} - diff --git a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/livechat/LiveChatInitRequest.kt b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/livechat/LiveChatInitRequest.kt index a35a35bd..8329ef0a 100644 --- a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/livechat/LiveChatInitRequest.kt +++ b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/livechat/LiveChatInitRequest.kt @@ -19,7 +19,7 @@ internal data class LiveChatInitRequest( return LiveChatInitRequest( request = req, switchData = reply, - sessionId = SessionManager.getOrCreateSessionId(lc).sessionId + sessionId = SessionManager.get(lc).getOrCreateSessionId().sessionId ) } } diff --git a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/JaicpConversationLogger.kt b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/JaicpConversationLogger.kt index d564b114..910f4b0b 100644 --- a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/JaicpConversationLogger.kt +++ b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/JaicpConversationLogger.kt @@ -2,13 +2,16 @@ package com.justai.jaicf.channel.jaicp.logging import com.justai.jaicf.api.BotRequest -import com.justai.jaicf.channel.jaicp.* +import com.justai.jaicf.channel.jaicp.DEFAULT_PROXY_URL +import com.justai.jaicf.channel.jaicp.JaicpPollingConnector +import com.justai.jaicf.channel.jaicp.JaicpWebhookConnector import com.justai.jaicf.channel.jaicp.dto.JaicpBotRequest import com.justai.jaicf.channel.jaicp.dto.JaicpLogModel import com.justai.jaicf.channel.jaicp.http.ChatAdapterConnector import com.justai.jaicf.channel.jaicp.http.HttpClientFactory -import com.justai.jaicf.channel.jaicp.logging.internal.SessionManager.getOrCreateSessionId +import com.justai.jaicf.channel.jaicp.jaicpRequest import com.justai.jaicf.channel.jaicp.logging.internal.SessionData +import com.justai.jaicf.channel.jaicp.logging.internal.SessionManager import com.justai.jaicf.helpers.logging.WithLogger import com.justai.jaicf.logging.ConversationLogObfuscator import com.justai.jaicf.logging.ConversationLogger @@ -46,7 +49,7 @@ open class JaicpConversationLogger( override fun doLog(loggingContext: LoggingContext) { try { val req = loggingContext.jaicpRequest ?: return - val session = getOrCreateSessionId(loggingContext) + val session = SessionManager.get(loggingContext).getOrCreateSessionId() launch { doLogAsync(req, loggingContext, session) } } catch (e: Exception) { logger.debug("Failed to produce JAICP LogRequest: ", e) diff --git a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/internal/SessionData.kt b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/internal/SessionData.kt deleted file mode 100644 index 1867f35d..00000000 --- a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/internal/SessionData.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.justai.jaicf.channel.jaicp.logging.internal - -import com.justai.jaicf.channel.jaicp.JSON -import com.justai.jaicf.channel.jaicp.context.ContextSerializable -import com.justai.jaicf.channel.jaicp.context.ContextSerializableService -import com.justai.jaicf.context.BotContext -import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString -import java.util.* - -internal object SessionDataService : ContextSerializableService { - override val key = "com/justai/jaicf/jaicp/logging/conversationSession/session" - override fun deserialize(content: String) = JSON.decodeFromString(SessionData.serializer(), content) -} - -@Serializable -internal data class SessionData( - val sessionId: String, - val isNewSession: Boolean -) : ContextSerializable { - - override fun serialized() = JSON.encodeToString(serializer(), copy(isNewSession = false)) - override val service: ContextSerializableService<*> = - SessionDataService - - companion object { - fun new(ctx: BotContext) = SessionData( - "${ctx.clientId}-${UUID.randomUUID()}", - true - ) - } -} \ No newline at end of file diff --git a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/internal/SessionEvent.kt b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/internal/SessionEvent.kt deleted file mode 100644 index 32475866..00000000 --- a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/internal/SessionEvent.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.justai.jaicf.channel.jaicp.logging.internal - -import com.justai.jaicf.channel.jaicp.JSON -import com.justai.jaicf.channel.jaicp.context.ContextSerializable -import com.justai.jaicf.channel.jaicp.context.ContextSerializableService -import kotlinx.serialization.Serializable - -internal object SessionEventService : ContextSerializableService { - override val key = "com/justai/jaicf/jaicp/logging/conversationSession/event" - override fun deserialize(content: String) = JSON.decodeFromString(SessionEvent.serializer(), content) -} - -@Serializable -internal sealed class SessionEvent : ContextSerializable { - override fun serialized() = JSON.encodeToString(serializer(), this) - override val service by lazy { SessionEventService } -} - -@Serializable -internal object SessionStarted : SessionEvent() - -@Serializable -internal object SessionEnded : SessionEvent() \ No newline at end of file diff --git a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/internal/SessionManager.kt b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/internal/SessionManager.kt index 133b8244..377fea70 100644 --- a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/internal/SessionManager.kt +++ b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/logging/internal/SessionManager.kt @@ -1,33 +1,84 @@ package com.justai.jaicf.channel.jaicp.logging.internal -import com.justai.jaicf.context.BotContext +import com.justai.jaicf.channel.jaicp.JSON +import com.justai.jaicf.channel.jaicp.reactions.reaction.EndSessionReaction +import com.justai.jaicf.channel.jaicp.reactions.reaction.NewSessionReaction import com.justai.jaicf.logging.LoggingContext +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import java.util.* -internal object SessionManager { - fun processStartSessionReaction(ctx: BotContext) = SessionStarted.saveToContext(ctx) +internal const val SESSION_MANAGER_KEY = "com/justai/jaicf/jaicp/logging/conversationSession/sessionManager" - fun processEndSessionReaction(ctx: BotContext) = SessionEnded.saveToContext(ctx) +@Serializable +internal data class SessionData( + val sessionId: String, + val isNewSession: Boolean +) - fun getOrCreateSessionId(loggingContext: LoggingContext): SessionData { - val ctx = loggingContext.botContext - val sessionData = SessionDataService.fromContext(ctx) - val sessionEvent = SessionEventService.fromContext(ctx) - SessionEventService.cleanup(ctx) +@Serializable +internal class SessionManager { + private var sessionId: String? = null + private var isNewSession: Boolean = false - if (sessionData == null || loggingContext.requestContext.newSession) { - return SessionData.new(ctx).apply { saveToContext(ctx) } - } + @Transient + private var thisRequestSessionData: SessionData? = null + + @Transient + lateinit var loggingContext: LoggingContext - return when (sessionEvent) { - // if session started, create new session and save it for further requests - SessionStarted -> SessionData.new(ctx).apply { saveToContext(ctx) } - // if session ended, delete current session - SessionEnded -> sessionData.also { - SessionDataService.cleanup(ctx) - ctx.cleanSessionData() + fun getOrCreateSessionId(): SessionData = thisRequestSessionData ?: runAndSave { + val sessionData = when { + hasNewSessionReaction() -> SessionData(createSessionId(), true) + hasEndSessionReaction() -> when (sessionId) { + null -> SessionData(createSessionId(), true) + else -> SessionData(requireNotNull(sessionId), false) } - null -> sessionData + shouldStartNewSession() -> SessionData(createSessionId(), true) + else -> SessionData(requireNotNull(sessionId), false) + + } + + sessionId = when (hasEndSessionReaction()) { + true -> null + false -> sessionData.sessionId + } + sessionData + } + + private fun hasNewSessionReaction() = loggingContext.reactions.any { it is NewSessionReaction } + + private fun hasEndSessionReaction() = loggingContext.reactions.any { it is EndSessionReaction } + + private fun shouldStartNewSession() = sessionId == null || loggingContext.requestContext.newSession + + private fun runAndSave(block: SessionManager.() -> SessionData): SessionData { + val sessionData = block.invoke(this) + thisRequestSessionData = sessionData + + loggingContext.botContext.session[SESSION_MANAGER_KEY] = JSON.encodeToString(serializer(), this) + loggingContext.botContext.temp[SESSION_MANAGER_KEY] = this + return sessionData + } + + private fun createSessionId() = "${loggingContext.botContext.clientId}-${UUID.randomUUID()}" + + override fun toString(): String { + return "SessionManager(sessionId=$sessionId, isNewSession=$isNewSession, thisRequestSessionData=$thisRequestSessionData, loggingContext=$loggingContext)" + } + + + companion object { + fun get(loggingContext: LoggingContext): SessionManager { + // if any component requested session, we finalize session and use it during all request processing + (loggingContext.botContext.temp[SESSION_MANAGER_KEY] as? SessionManager)?.let { return it } + + // this is the first call for sessionData, we try to decode it from BotContext and reuse. + val storedSessionState = loggingContext.botContext.session[SESSION_MANAGER_KEY] as? String + val sessionManager = storedSessionState?.let { JSON.decodeFromString(serializer(), it) } ?: SessionManager() + sessionManager.loggingContext = loggingContext + return sessionManager } } -} +} \ No newline at end of file diff --git a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/reactions/JaicpReactions.kt b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/reactions/JaicpReactions.kt index fe6d299e..c4afdebd 100755 --- a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/reactions/JaicpReactions.kt +++ b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/reactions/JaicpReactions.kt @@ -2,6 +2,8 @@ package com.justai.jaicf.channel.jaicp.reactions import com.justai.jaicf.channel.jaicp.dto.* import com.justai.jaicf.channel.jaicp.logging.internal.SessionManager +import com.justai.jaicf.channel.jaicp.reactions.reaction.EndSessionReaction +import com.justai.jaicf.channel.jaicp.reactions.reaction.NewSessionReaction import com.justai.jaicf.channel.jaicp.toJson import com.justai.jaicf.context.DialogContext import com.justai.jaicf.logging.SayReaction @@ -13,6 +15,7 @@ val Reactions.jaicp get() = this as? JaicpReactions open class JaicpReactions : Reactions() { protected val replies: MutableList = mutableListOf() + internal val dialer by lazy { JaicpDialerAPI() } internal fun getCurrentState() = botContext.dialogContext.currentState @@ -32,7 +35,7 @@ open class JaicpReactions : Reactions() { * */ fun startNewSession() { botContext.cleanSessionData() - SessionManager.processStartSessionReaction(botContext) + registerReaction(NewSessionReaction(getCurrentState())) } /** @@ -48,7 +51,7 @@ open class JaicpReactions : Reactions() { fun endSession() { botContext.dialogContext.currentState = "/" botContext.dialogContext.currentContext = "/" - SessionManager.processEndSessionReaction(botContext) + registerReaction(EndSessionReaction(getCurrentState())) } fun collect(): JsonObject { @@ -67,7 +70,7 @@ open class JaicpReactions : Reactions() { putJsonArray("replies") { jsonReplies.forEach { add(it) } } - put("sessionId", SessionManager.getOrCreateSessionId(loggingContext).sessionId) + put("sessionId", SessionManager.get(loggingContext).getOrCreateSessionId().sessionId) put("answer", answer) } } diff --git a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/reactions/reaction/EndSessionReaction.kt b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/reactions/reaction/EndSessionReaction.kt new file mode 100644 index 00000000..7d743255 --- /dev/null +++ b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/reactions/reaction/EndSessionReaction.kt @@ -0,0 +1,7 @@ +package com.justai.jaicf.channel.jaicp.reactions.reaction + +import com.justai.jaicf.logging.Reaction + +data class EndSessionReaction( + override val fromState: String +) : Reaction(fromState) \ No newline at end of file diff --git a/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/reactions/reaction/NewSessionReaction.kt b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/reactions/reaction/NewSessionReaction.kt new file mode 100644 index 00000000..4ce6339b --- /dev/null +++ b/channels/jaicp/src/main/kotlin/com/justai/jaicf/channel/jaicp/reactions/reaction/NewSessionReaction.kt @@ -0,0 +1,7 @@ +package com.justai.jaicf.channel.jaicp.reactions.reaction + +import com.justai.jaicf.logging.Reaction + +data class NewSessionReaction( + override val fromState: String +) : Reaction(fromState) \ No newline at end of file