diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiHandler.kt
index b11395f..c7b3d85 100644
--- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiHandler.kt
+++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ApiHandler.kt
@@ -9,15 +9,16 @@ import javax.servlet.http.HttpServletResponse
interface ApiHandler {
fun route(request: HttpServletRequest, response: HttpServletResponse) =
+ // for now, only get() needed the response object ; other methods shall be reengineered as well if needed
when (request.method) {
- "GET" -> get(request)
+ "GET" -> get(request, response)
"POST" -> post(request)
"PUT" -> put(request)
"DELETE" -> delete(request)
else -> notImplemented()
}
- fun get(request: HttpServletRequest): Json {
+ fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
notImplemented()
}
diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt
index e997637..9ba2727 100644
--- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt
+++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PairingHandler.kt
@@ -8,10 +8,11 @@ import org.jeudego.pairgoth.model.toJson
import org.jeudego.pairgoth.web.Event
import org.jeudego.pairgoth.web.Event.*
import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
object PairingHandler: PairgothApiHandler {
- override fun get(request: HttpServletRequest): Json {
+ override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
val tournament = getTournament(request)
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
val playing = (tournament.games.getOrNull(round)?.values ?: emptyList()).flatMap {
diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PlayerHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PlayerHandler.kt
index 70691f5..d6ac610 100644
--- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PlayerHandler.kt
+++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/PlayerHandler.kt
@@ -8,10 +8,11 @@ import org.jeudego.pairgoth.model.fromJson
import org.jeudego.pairgoth.web.Event
import org.jeudego.pairgoth.web.Event.*
import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
object PlayerHandler: PairgothApiHandler {
- override fun get(request: HttpServletRequest): Json {
+ override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
val tournament = getTournament(request) ?: badRequest("invalid tournament")
return when (val pid = getSubSelector(request)?.toIntOrNull()) {
null -> tournament.pairables.values.map { it.toJson() }.toJsonArray()
diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ResultsHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ResultsHandler.kt
index ed3b869..bac6b0a 100644
--- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ResultsHandler.kt
+++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/ResultsHandler.kt
@@ -4,15 +4,14 @@ import com.republicate.kson.Json
import com.republicate.kson.toJsonArray
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.model.Game
-import org.jeudego.pairgoth.model.Tournament
import org.jeudego.pairgoth.model.toJson
-import org.jeudego.pairgoth.store.Store
import org.jeudego.pairgoth.web.Event
import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
object ResultsHandler: PairgothApiHandler {
- override fun get(request: HttpServletRequest): Json {
+ override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
val tournament = getTournament(request)
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
val games = tournament.games.getOrNull(round)?.values ?: emptyList()
diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt
index 715ee7a..c1c612b 100644
--- a/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt
+++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/api/TournamentHandler.kt
@@ -8,17 +8,30 @@ import org.jeudego.pairgoth.model.Tournament
import org.jeudego.pairgoth.model.fromJson
import org.jeudego.pairgoth.model.toJson
import org.jeudego.pairgoth.store.Store
+import org.jeudego.pairgoth.web.ApiServlet
import org.jeudego.pairgoth.web.Event
import org.jeudego.pairgoth.web.Event.*
import org.w3c.dom.Element
import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
object TournamentHandler: PairgothApiHandler {
- override fun get(request: HttpServletRequest): Json {
+ override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
+ val accept = request.getHeader("Accept")
return when (val id = getSelector(request)?.toIntOrNull()) {
null -> Json.Array(Store.getTournamentsIDs())
- else -> Store.getTournament(id)?.toJson() ?: badRequest("no tournament with id #${id}")
+ else ->
+ when {
+ ApiServlet.isJson(accept) -> Store.getTournament(id)?.toJson() ?: badRequest("no tournament with id #${id}")
+ ApiServlet.isXml(accept) -> {
+ val export = Store.getTournament(id)?.let { OpenGotha.export(it) } ?: badRequest("no tournament with id #${id}")
+ response.contentType = "application/xml; charset=UTF-8"
+ response.writer.write(export)
+ null // return null to indicate that we handled the response ourself
+ }
+ else -> badRequest("unhandled Accept header: $accept")
+ }
}
}
diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/ext/OpenGotha.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/ext/OpenGotha.kt
index 70377c5..861dfe2 100644
--- a/webapp/src/main/kotlin/org/jeudego/pairgoth/ext/OpenGotha.kt
+++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/ext/OpenGotha.kt
@@ -9,7 +9,9 @@ import org.jeudego.pairgoth.model.Player
import org.jeudego.pairgoth.model.StandardByoyomi
import org.jeudego.pairgoth.model.SuddenDeath
import org.jeudego.pairgoth.model.Swiss
+import org.jeudego.pairgoth.model.TimeSystem
import org.jeudego.pairgoth.model.Tournament
+import org.jeudego.pairgoth.model.displayRank
import org.jeudego.pairgoth.model.parseRank
import org.jeudego.pairgoth.store.Store
import org.jeudego.pairgoth.util.XmlFormat
@@ -173,4 +175,110 @@ object OpenGotha {
tournament.games.addAll(gamesPerRound)
return tournament
}
+
+ // TODO - bye player(s)
+ fun export(tournament: Tournament): String {
+ val xml = """
+
+
+
+ ${tournament.pairables.values.map { player ->
+ player as Player
+ }.joinToString("\n") { player ->
+ """"""
+ }
+ }
+
+
+ ${tournament.games.flatMapIndexed { round, games ->
+ games.values.mapIndexed { table, game ->
+ Triple(round, table , game)
+ }
+ }.joinToString("\n") { (round, table, game) ->
+ """ "RESULT_BLACKWINS"
+ Game.Result.WHITE -> "RESULT_WHITEWINS"
+ Game.Result.JIGO -> "RESULT_EQUAL"
+ Game.Result.BOTHWIN -> "RESULT_BOTHWIN"
+ Game.Result.BOTHLOOSE -> "RESULT_BOTHLOOSE"
+ else -> throw Error("unhandled game result")
+ }
+ }" roundNumber="${
+ round + 1
+ }" tableNumber="${
+ table + 1
+ }" whitePlayer="${
+ (tournament.pairables[game.white]!! as Player).let { white ->
+ "${white.name}${white.firstname}".uppercase(Locale.ENGLISH) // Use Locale.ENGLISH to transform é to É
+ }
+ }"/>"""
+ }
+ }
+
+
+
+
+ "STDBYOYOMI"
+ TimeSystem.TimeSystemType.CANADIAN -> "CANBYOYOMI"
+ TimeSystem.TimeSystemType.FISCHER -> "FISCHER"
+ } }" director="" endDate="${tournament.endDate}" fischerTime="${tournament.timeSystem.increment}" genCountNotPlayedGamesAsHalfPoint="false" genMMBar="9D" genMMFloor="30K" genMMS2ValueAbsent="1" genMMS2ValueBye="2" genMMZero="30K" genNBW2ValueAbsent="0" genNBW2ValueBye="2" genRoundDownNBWMMS="true" komi="${tournament.komi}" location="${tournament.location}" name="${tournament.name}" nbMovesCanTime="${tournament.timeSystem.stones}" numberOfCategories="1" numberOfRounds="${tournament.rounds}" shortName="${tournament.shortName}" size="${tournament.gobanSize}" stdByoYomiTime="${tournament.timeSystem.byoyomi}"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """.trimIndent()
+ return xml
+ }
}
diff --git a/webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt b/webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt
index 229e972..2d0834a 100644
--- a/webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt
+++ b/webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt
@@ -213,7 +213,7 @@ class ApiServlet : HttpServlet() {
HttpServletResponse.SC_BAD_REQUEST,
"Missing 'Accept' header"
)
- if (!isJson(accept)) throw ApiException(
+ if (!isJson(accept) && (!isXml(accept) || !request.requestURI.matches(Regex("/api/tour/\\d+")))) throw ApiException(
HttpServletResponse.SC_BAD_REQUEST,
"Invalid 'Accept' header"
)
@@ -260,7 +260,7 @@ class ApiServlet : HttpServlet() {
private const val EXPECTED_CHARSET = "utf8"
const val AUTH_HEADER = "Authorization"
const val AUTH_PREFIX = "Bearer"
- private fun isJson(mimeType: String) = "text/json" == mimeType || "application/json" == mimeType || mimeType.endsWith("+json")
- private fun isXml(mimeType: String) = "text/xml" == mimeType || "application/xml" == mimeType || mimeType.endsWith("+xml")
+ fun isJson(mimeType: String) = "text/json" == mimeType || "application/json" == mimeType || mimeType.endsWith("+json")
+ fun isXml(mimeType: String) = "text/xml" == mimeType || "application/xml" == mimeType || mimeType.endsWith("+xml")
}
}
diff --git a/webapp/src/test/kotlin/ImportTests.kt b/webapp/src/test/kotlin/ImportExportTests.kt
similarity index 88%
rename from webapp/src/test/kotlin/ImportTests.kt
rename to webapp/src/test/kotlin/ImportExportTests.kt
index ceaab92..1874442 100644
--- a/webapp/src/test/kotlin/ImportTests.kt
+++ b/webapp/src/test/kotlin/ImportExportTests.kt
@@ -1,10 +1,9 @@
package org.jeudego.pairgoth.test
import org.junit.jupiter.api.Test
-import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
-class ImportTests: TestBase() {
+class ImportExportTests: TestBase() {
@Test
fun `001 test imports`() {
@@ -21,6 +20,8 @@ class ImportTests: TestBase() {
val games = TestAPI.get("/api/tour/$id/res/1").asArray()
logger.info("games for round $round: {}", games.toString())
}
+ val xml = TestAPI.getXml("/api/tour/$id")
+ logger.info(xml)
}
}
}
diff --git a/webapp/src/test/kotlin/TestUtils.kt b/webapp/src/test/kotlin/TestUtils.kt
index 7d6dd78..da44a2b 100644
--- a/webapp/src/test/kotlin/TestUtils.kt
+++ b/webapp/src/test/kotlin/TestUtils.kt
@@ -34,7 +34,7 @@ object TestAPI {
private val apiServlet = ApiServlet()
private val sseServlet = SSEServlet()
- private fun testRequest(reqMethod: String, uri: String, payload: T? = null): Json {
+ private fun testRequest(reqMethod: String, uri: String, accept: String = "application/json", payload: T? = null): String {
WebappManager.properties["webapp.env"] = "test"
@@ -64,7 +64,7 @@ object TestAPI {
else -> throw Error("unhandled case")
}
on { headerNames } doReturn Collections.enumeration(myHeaderNames)
- on { getHeader(eq("Accept")) } doReturn "application/json"
+ on { getHeader(eq("Accept")) } doReturn accept
}
// mock response
@@ -83,13 +83,14 @@ object TestAPI {
"DELETE" -> apiServlet.doDelete(req, resp)
}
- return Json.parse(buffer.toString()) ?: throw Error("no response payload")
+ return buffer.toString() ?: throw Error("no response payload")
}
- fun get(uri: String) = testRequest("GET", uri)
- fun post(uri: String, payload: T) = testRequest("POST", uri, payload)
- fun put(uri: String, payload: T) = testRequest("PUT", uri, payload)
- fun delete(uri: String, payload: T) = testRequest("DELETE", uri, payload)
+ fun get(uri: String): Json = Json.parse(testRequest("GET", uri)) ?: throw Error("no payload")
+ fun getXml(uri: String): String = testRequest("GET", uri, "application/xml")
+ fun post(uri: String, payload: T) = Json.parse(testRequest("POST", uri, payload = payload)) ?: throw Error("no payload")
+ fun put(uri: String, payload: T) = Json.parse(testRequest("PUT", uri, payload = payload)) ?: throw Error("no payload")
+ fun delete(uri: String, payload: T) = Json.parse(testRequest("DELETE", uri, payload = payload)) ?: throw Error("no payload")
}
// Get a list of resources