diff --git a/client/build.gradle.kts b/client/build.gradle.kts index e35b72d..f66deee 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -14,6 +14,8 @@ dependencies { implementation("com.miglayout:miglayout-swing:5.2") implementation("com.konghq:unirest-java:3.14.2") implementation("ch.qos.logback:logback-classic:1.5.8") + implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.16.1") implementation(project(":core")) testImplementation(project(":test-core")) testImplementation("com.squareup.okhttp3:mockwebserver:4.12.0") diff --git a/client/src/main/java/online/screen/EntropyLobby.java b/client/src/main/java/online/screen/EntropyLobby.java index a9409e0..b193040 100644 --- a/client/src/main/java/online/screen/EntropyLobby.java +++ b/client/src/main/java/online/screen/EntropyLobby.java @@ -1,7 +1,7 @@ package online.screen; import game.GameMode; -import http.dto.LobbyResponse; +import http.dto.LobbyMessage; import http.dto.OnlineUser; import http.dto.RoomSummary; import object.RoomTable; @@ -137,7 +137,7 @@ public EntropyLobby() private final JButton btnSettings = new JButton(""); private final OnlineChatPanel chatPanel = new OnlineChatPanel(LOBBY_ID); - public void init(LobbyResponse lobbyResponse) + public void init(LobbyMessage lobbyMessage) { SwingUtilities.invokeLater(new Runnable() { @@ -157,7 +157,7 @@ public void run() initChatPanelIfNecessary(); - syncLobbyOnEventThread(lobbyResponse); + syncLobbyOnEventThread(lobbyMessage); } }); @@ -227,14 +227,14 @@ public GameRoom createGameRoom(RoomSummary room) return gameRoom; } - public void syncLobby(LobbyResponse response) + public void syncLobby(LobbyMessage response) { SwingUtilities.invokeLater(() -> { syncLobbyOnEventThread(response); }); } - private void syncLobbyOnEventThread(LobbyResponse response) { + private void syncLobbyOnEventThread(LobbyMessage response) { roomTable.synchroniseRooms(response.getRooms()); synchUsernames(response.getUsers()); } diff --git a/client/src/main/kotlin/http/WebSocketReceiver.kt b/client/src/main/kotlin/http/WebSocketReceiver.kt index 57edaad..aeff512 100644 --- a/client/src/main/kotlin/http/WebSocketReceiver.kt +++ b/client/src/main/kotlin/http/WebSocketReceiver.kt @@ -1,17 +1,21 @@ package http import http.dto.ClientMessage -import http.dto.LobbyResponse -import kong.unirest.JsonObjectMapper +import http.dto.LobbyMessage import screen.ScreenCache +import utils.CoreGlobals import utils.CoreGlobals.logger class WebSocketReceiver { - private val jsonObjectMapper = JsonObjectMapper() - fun receiveMessage(rawMessage: String) { + val clientMessage = deserializeClientMessage(rawMessage) + logger.info( + "serverMessage", + "Received server message of type ${clientMessage::class.simpleName}" + ) + when (val clientMessage = deserializeClientMessage(rawMessage)) { - is LobbyResponse -> handleLobbyResponse(clientMessage) + is LobbyMessage -> handleLobbyResponse(clientMessage) } } @@ -29,9 +33,9 @@ class WebSocketReceiver { } private fun deserializeClientMessage(rawMessage: String): ClientMessage = - jsonObjectMapper.readValue(rawMessage, ClientMessage::class.java) + CoreGlobals.jsonMapper.readValue(rawMessage, ClientMessage::class.java) - private fun handleLobbyResponse(clientMessage: LobbyResponse) { + private fun handleLobbyResponse(clientMessage: LobbyMessage) { ScreenCache.getEntropyLobby().syncLobby(clientMessage) } } diff --git a/client/src/test/kotlin/http/SessionApiTest.kt b/client/src/test/kotlin/http/SessionApiTest.kt index b5dd9bc..44660d6 100644 --- a/client/src/test/kotlin/http/SessionApiTest.kt +++ b/client/src/test/kotlin/http/SessionApiTest.kt @@ -6,7 +6,7 @@ import com.github.alyssaburlton.swingtest.flushEdt import http.Routes.BEGIN_SESSION import http.dto.BeginSessionRequest import http.dto.BeginSessionResponse -import http.dto.LobbyResponse +import http.dto.LobbyMessage import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk @@ -104,12 +104,12 @@ class SessionApiTest : AbstractTest() { val mockLobby = mockk(relaxed = true) ScreenCache.setEntropyLobby(mockLobby) - val lobbyResponse = LobbyResponse(emptyList(), emptyList()) + val lobbyMessage = LobbyMessage(emptyList(), emptyList()) val httpClient = mockHttpClient( SuccessResponse( 200, - BeginSessionResponse("alyssa", UUID.randomUUID(), lobbyResponse) + BeginSessionResponse("alyssa", UUID.randomUUID(), lobbyMessage) ) ) @@ -119,7 +119,7 @@ class SessionApiTest : AbstractTest() { verify { lobby.username = "alyssa" lobby.isVisible = true - lobby.init(lobbyResponse) + lobby.init(lobbyMessage) } } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 50d7485..a360e28 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -12,6 +12,7 @@ ktfmt { kotlinLangStyle() } dependencies { implementation("javax.activation:activation:1.1.1") implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.16.1") implementation("ch.qos.logback:logback-classic:1.5.8") implementation("net.logstash.logback:logstash-logback-encoder:8.0") testImplementation(project(":test-core")) diff --git a/core/src/main/java/util/XmlUtil.java b/core/src/main/java/util/XmlUtil.java index 7435862..833bed2 100644 --- a/core/src/main/java/util/XmlUtil.java +++ b/core/src/main/java/util/XmlUtil.java @@ -68,8 +68,12 @@ public static String getStringFromDocument(Document xmlDoc) return ""; } } + + public static Document getDocumentFromXmlString(String xmlStr) { + return getDocumentFromXmlString(xmlStr, false); + } - public static Document getDocumentFromXmlString(String xmlStr) + public static Document getDocumentFromXmlString(String xmlStr, boolean suppressError) { try { @@ -79,7 +83,9 @@ public static Document getDocumentFromXmlString(String xmlStr) } catch (Throwable t) { - CoreGlobals.logger.error("xmlParseError", "Failed to parse XML " + xmlStr, t); + if (!suppressError) { + CoreGlobals.logger.error("xmlParseError", "Failed to parse XML " + xmlStr, t); + } return null; } } diff --git a/core/src/main/kotlin/http/ClientMessageType.kt b/core/src/main/kotlin/http/ClientMessageType.kt deleted file mode 100644 index 54d14ee..0000000 --- a/core/src/main/kotlin/http/ClientMessageType.kt +++ /dev/null @@ -1,5 +0,0 @@ -package http - -enum class ClientMessageType { - LOBBY -} diff --git a/core/src/main/kotlin/http/dto/BeginSessionResponse.kt b/core/src/main/kotlin/http/dto/BeginSessionResponse.kt index 5ca6db2..3f1b225 100644 --- a/core/src/main/kotlin/http/dto/BeginSessionResponse.kt +++ b/core/src/main/kotlin/http/dto/BeginSessionResponse.kt @@ -2,4 +2,4 @@ package http.dto import java.util.* -data class BeginSessionResponse(val name: String, val sessionId: UUID, val lobby: LobbyResponse) +data class BeginSessionResponse(val name: String, val sessionId: UUID, val lobby: LobbyMessage) diff --git a/core/src/main/kotlin/http/dto/ClientMessage.kt b/core/src/main/kotlin/http/dto/ClientMessage.kt index 2e1827c..1299ab5 100644 --- a/core/src/main/kotlin/http/dto/ClientMessage.kt +++ b/core/src/main/kotlin/http/dto/ClientMessage.kt @@ -2,14 +2,11 @@ package http.dto import com.fasterxml.jackson.annotation.JsonSubTypes import com.fasterxml.jackson.annotation.JsonTypeInfo -import http.ClientMessageType @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, + include = JsonTypeInfo.As.PROPERTY, property = "messageType", ) -@JsonSubTypes(JsonSubTypes.Type(value = LobbyResponse::class, name = "LOBBY")) -sealed class ClientMessage { - abstract val messageType: ClientMessageType -} +@JsonSubTypes(JsonSubTypes.Type(value = LobbyMessage::class, name = "LOBBY")) +abstract class ClientMessage diff --git a/core/src/main/kotlin/http/dto/LobbyMessage.kt b/core/src/main/kotlin/http/dto/LobbyMessage.kt new file mode 100644 index 0000000..26a7106 --- /dev/null +++ b/core/src/main/kotlin/http/dto/LobbyMessage.kt @@ -0,0 +1,4 @@ +package http.dto + +data class LobbyMessage(val rooms: List, val users: List) : + ClientMessage() diff --git a/core/src/main/kotlin/http/dto/LobbyResponse.kt b/core/src/main/kotlin/http/dto/LobbyResponse.kt deleted file mode 100644 index 6ae24a0..0000000 --- a/core/src/main/kotlin/http/dto/LobbyResponse.kt +++ /dev/null @@ -1,8 +0,0 @@ -package http.dto - -import http.ClientMessageType - -data class LobbyResponse(val rooms: List, val users: List) : - ClientMessage() { - override val messageType = ClientMessageType.LOBBY -} diff --git a/core/src/main/kotlin/utils/CoreGlobals.kt b/core/src/main/kotlin/utils/CoreGlobals.kt index c693f8e..742467b 100644 --- a/core/src/main/kotlin/utils/CoreGlobals.kt +++ b/core/src/main/kotlin/utils/CoreGlobals.kt @@ -1,7 +1,8 @@ package utils import ch.qos.logback.classic.Logger as LogbackLogger -import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.json.JsonMapper +import com.fasterxml.jackson.module.kotlin.registerKotlinModule import java.time.Clock import logging.Logger import org.slf4j.LoggerFactory @@ -10,5 +11,5 @@ object CoreGlobals { val slf4jLogger: LogbackLogger = LoggerFactory.getLogger("entropy") as LogbackLogger @JvmField var logger: Logger = Logger(slf4jLogger) var clock: Clock = Clock.systemUTC() - val objectMapper = ObjectMapper() + val jsonMapper = JsonMapper().also { it.registerKotlinModule() } } diff --git a/core/src/test/kotlin/http/dto/ClientMessageTest.kt b/core/src/test/kotlin/http/dto/ClientMessageTest.kt new file mode 100644 index 0000000..4616d38 --- /dev/null +++ b/core/src/test/kotlin/http/dto/ClientMessageTest.kt @@ -0,0 +1,20 @@ +package http.dto + +import com.fasterxml.jackson.databind.json.JsonMapper +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import testCore.AbstractTest + +class ClientMessageTest : AbstractTest() { + @Test + fun `Should be able to deserialize based on messageType`() { + val message = LobbyMessage(emptyList(), listOf(OnlineUser("Alyssa"))) + + val mapper = JsonMapper().registerKotlinModule() + val json = mapper.writeValueAsString(message) + + val deserialized = mapper.readValue(json, ClientMessage::class.java) + deserialized shouldBe message + } +} diff --git a/server/src/main/java/server/MessageHandlerRunnable.java b/server/src/main/java/server/MessageHandlerRunnable.java index f60c5cd..489efee 100644 --- a/server/src/main/java/server/MessageHandlerRunnable.java +++ b/server/src/main/java/server/MessageHandlerRunnable.java @@ -154,7 +154,7 @@ private void initVariablesFromSocket() */ private Document getResponse(String encryptedMessage) throws Throwable { - Document unencryptedDocument = XmlUtil.getDocumentFromXmlString(encryptedMessage); + Document unencryptedDocument = XmlUtil.getDocumentFromXmlString(encryptedMessage, true); if (unencryptedDocument != null) { //We've been sent an unencrypted XML message. Either this is the client agreeing a new diff --git a/server/src/main/kotlin/routes/lobby/LobbyService.kt b/server/src/main/kotlin/routes/lobby/LobbyService.kt index 7bd1499..641eaa8 100644 --- a/server/src/main/kotlin/routes/lobby/LobbyService.kt +++ b/server/src/main/kotlin/routes/lobby/LobbyService.kt @@ -3,20 +3,20 @@ package routes.lobby import auth.UserConnection import game.GameMode import game.GameSettings -import http.dto.LobbyResponse +import http.dto.LobbyMessage import http.dto.OnlineUser import http.dto.RoomSummary import `object`.Room import util.GameConstants import util.ServerGlobals import util.XmlConstants -import utils.CoreGlobals.objectMapper +import utils.CoreGlobals class LobbyService { - fun getLobby(): LobbyResponse { + fun getLobby(): LobbyMessage { val rooms = ServerGlobals.server.rooms.map { it.toSummary() } val users = ServerGlobals.sessionStore.getAll().map { OnlineUser(it.name) } - return LobbyResponse(rooms, users) + return LobbyMessage(rooms, users) } @JvmOverloads @@ -29,7 +29,7 @@ class LobbyService { val lobbyMessage = getLobby() ServerGlobals.server.sendViaNotificationSocket( usersToNotify, - objectMapper.writeValueAsString(lobbyMessage), + CoreGlobals.jsonMapper.writeValueAsString(lobbyMessage), XmlConstants.SOCKET_NAME_LOBBY, ) }