aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Jaros <rjaros@finn.pl>2019-10-12 18:22:43 +0200
committerRobert Jaros <rjaros@finn.pl>2019-10-12 18:22:43 +0200
commitbcf7504392baccf1568e740c1d453eac7080fb5b (patch)
tree519489751032814bfc0d5426c48de257b788329f
parent28b6e9d646123d774660ebdf0124eeb8a22fe087 (diff)
downloadkvision-bcf7504392baccf1568e740c1d453eac7080fb5b.tar.gz
kvision-bcf7504392baccf1568e740c1d453eac7080fb5b.tar.bz2
kvision-bcf7504392baccf1568e740c1d453eac7080fb5b.zip
Redesign spring-boot server module to use Spring WebFlux instead of Spring MVC.
-rw-r--r--gradle.properties5
-rw-r--r--kvision-modules/kvision-common-remote/src/main/kotlin/pl/treksoft/kvision/remote/Annotations.kt38
-rw-r--r--kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/RemoteAgent.kt3
-rw-r--r--kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt23
-rw-r--r--kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt22
-rw-r--r--kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/Profile.kt8
-rw-r--r--kvision-modules/kvision-server-spring-boot/build.gradle16
-rw-r--r--kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/Annotations.kt35
-rw-r--r--kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVController.kt66
-rw-r--r--kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVRouterConfiguration.kt84
-rw-r--r--kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt369
-rw-r--r--kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVWebSocketConfig.kt263
-rw-r--r--kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/Profile.kt137
-rw-r--r--kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/Security.kt30
-rw-r--r--kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/SessionInterfaces.kt47
-rw-r--r--kvision-modules/kvision-server-spring-boot/src/main/resources/META-INF/spring.factories2
16 files changed, 668 insertions, 480 deletions
diff --git a/gradle.properties b/gradle.properties
index 59d36c5e..3ac77b8c 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
group=pl.treksoft
-version=2.0.0-M1
+version=2.0.0-SNAPSHOT
kotlinVersion=1.3.50
javaVersion=1.8
coroutinesVersion=1.3.1
@@ -9,12 +9,13 @@ dokkaVersion=0.9.18
detektVersion=1.0.0-RC14
junitVersion=4.12
joobyVersion=1.6.4
-springBootVersion=2.1.8.RELEASE
+springBootVersion=2.2.0.RC1
ktorVersion=1.2.4
guiceVersion=4.2.2
pac4jVersion=3.5.0
dependencyManagementPluginVersion=1.0.4.RELEASE
jacksonModuleKotlinVersion=2.9.10
+springDataRelationalVersion=1.1.0.RELEASE
jqueryKotlinVersion=0.0.4
snabbdomKotlinVersion=0.1.1
navigoKotlinVersion=0.0.3
diff --git a/kvision-modules/kvision-common-remote/src/main/kotlin/pl/treksoft/kvision/remote/Annotations.kt b/kvision-modules/kvision-common-remote/src/main/kotlin/pl/treksoft/kvision/remote/Annotations.kt
new file mode 100644
index 00000000..997f0369
--- /dev/null
+++ b/kvision-modules/kvision-common-remote/src/main/kotlin/pl/treksoft/kvision/remote/Annotations.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision.remote
+
+@UseExperimental(ExperimentalMultiplatform::class)
+@OptionalExpectation
+expect annotation class Id()
+
+@UseExperimental(ExperimentalMultiplatform::class)
+@OptionalExpectation
+expect annotation class Table()
+
+@UseExperimental(ExperimentalMultiplatform::class)
+@OptionalExpectation
+expect annotation class Column()
+
+@UseExperimental(ExperimentalMultiplatform::class)
+@OptionalExpectation
+expect annotation class Transient()
diff --git a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/RemoteAgent.kt b/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/RemoteAgent.kt
index 06e150e4..1fb90507 100644
--- a/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/RemoteAgent.kt
+++ b/kvision-modules/kvision-remote/src/main/kotlin/pl/treksoft/kvision/remote/RemoteAgent.kt
@@ -35,6 +35,7 @@ import kotlinx.serialization.internal.StringSerializer
import kotlinx.serialization.list
import kotlinx.serialization.serializer
import pl.treksoft.kvision.types.JsonDateSerializer
+import pl.treksoft.kvision.types.toStringInternal
import kotlin.js.Date
import kotlin.reflect.KClass
@@ -113,7 +114,7 @@ interface RemoteAgent {
is Enum<*> -> "\"$value\""
is String -> value
is Char -> "\"$value\""
- is Date -> "\"${value.getTime()}\""
+ is Date -> "\"${value.toStringInternal()}\""
else -> try {
@Suppress("UNCHECKED_CAST")
JSON.plain.stringify(kClass.serializer(), value)
diff --git a/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt b/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt
index 4c4d4af0..e2a26967 100644
--- a/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt
+++ b/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt
@@ -21,6 +21,7 @@
*/
package pl.treksoft.kvision.remote
+import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.google.inject.Injector
import kotlinx.coroutines.CoroutineStart
@@ -37,7 +38,14 @@ import org.jooby.Request
import org.jooby.Response
import org.slf4j.Logger
import org.slf4j.LoggerFactory
+import pl.treksoft.kvision.types.*
import kotlin.reflect.KClass
+import java.time.LocalDateTime
+import java.time.LocalDate
+import java.time.LocalTime
+import java.time.OffsetDateTime
+import java.time.OffsetTime
+
/**
* Multiplatform service manager for Jooby.
@@ -51,7 +59,20 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass:
}
val routes: MutableList<Kooby.() -> Unit> = mutableListOf()
- val mapper = jacksonObjectMapper()
+ val mapper = jacksonObjectMapper().apply {
+ val module = SimpleModule()
+ module.addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer())
+ module.addSerializer(LocalDate::class.java, LocalDateSerializer())
+ module.addSerializer(LocalTime::class.java, LocalTimeSerializer())
+ module.addSerializer(OffsetDateTime::class.java, OffsetDateTimeSerializer())
+ module.addSerializer(OffsetTime::class.java, OffsetTimeSerializer())
+ module.addDeserializer(LocalDateTime::class.java, LocalDateTimeDeserializer())
+ module.addDeserializer(LocalDate::class.java, LocalDateDeserializer())
+ module.addDeserializer(LocalTime::class.java, LocalTimeDeserializer())
+ module.addDeserializer(OffsetDateTime::class.java, OffsetDateTimeDeserializer())
+ module.addDeserializer(OffsetTime::class.java, OffsetTimeDeserializer())
+ this.registerModule(module)
+ }
var counter: Int = 0
/**
diff --git a/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt b/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt
index 7ccd6341..36a7897e 100644
--- a/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt
+++ b/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt
@@ -21,6 +21,7 @@
*/
package pl.treksoft.kvision.remote
+import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.ktor.application.ApplicationCall
import io.ktor.application.call
@@ -49,6 +50,12 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import org.slf4j.Logger
import org.slf4j.LoggerFactory
+import pl.treksoft.kvision.types.*
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.LocalTime
+import java.time.OffsetDateTime
+import java.time.OffsetTime
import kotlin.reflect.KClass
/**
@@ -73,7 +80,20 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass:
val webSocketRequests: MutableMap<String, suspend WebSocketServerSession.() -> Unit> =
mutableMapOf()
- val mapper = jacksonObjectMapper()
+ val mapper = jacksonObjectMapper().apply {
+ val module = SimpleModule()
+ module.addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer())
+ module.addSerializer(LocalDate::class.java, LocalDateSerializer())
+ module.addSerializer(LocalTime::class.java, LocalTimeSerializer())
+ module.addSerializer(OffsetDateTime::class.java, OffsetDateTimeSerializer())
+ module.addSerializer(OffsetTime::class.java, OffsetTimeSerializer())
+ module.addDeserializer(LocalDateTime::class.java, LocalDateTimeDeserializer())
+ module.addDeserializer(LocalDate::class.java, LocalDateDeserializer())
+ module.addDeserializer(LocalTime::class.java, LocalTimeDeserializer())
+ module.addDeserializer(OffsetDateTime::class.java, OffsetDateTimeDeserializer())
+ module.addDeserializer(OffsetTime::class.java, OffsetTimeDeserializer())
+ this.registerModule(module)
+ }
var counter: Int = 0
/**
diff --git a/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/Profile.kt b/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/Profile.kt
index 693c8e3c..cc77f341 100644
--- a/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/Profile.kt
+++ b/kvision-modules/kvision-server-ktor/src/main/kotlin/pl/treksoft/kvision/remote/Profile.kt
@@ -25,7 +25,6 @@ import io.ktor.application.ApplicationCall
import io.ktor.sessions.get
import io.ktor.sessions.sessions
import kotlinx.serialization.Serializable
-import kotlinx.serialization.Transient
/**
* A user profile.
@@ -41,7 +40,6 @@ actual data class Profile(
val remembered: Boolean = false,
val clientName: String? = null
) {
- @Transient
var username: String?
get() = attributes["username"]
set(value) {
@@ -51,7 +49,6 @@ actual data class Profile(
attributes.remove("username")
}
}
- @Transient
var firstName: String?
get() = attributes["first_name"]
set(value) {
@@ -61,7 +58,6 @@ actual data class Profile(
attributes.remove("first_name")
}
}
- @Transient
var familyName: String?
get() = attributes["family_name"]
set(value) {
@@ -71,7 +67,6 @@ actual data class Profile(
attributes.remove("family_name")
}
}
- @Transient
var displayName: String?
get() = attributes["display_name"]
set(value) {
@@ -81,7 +76,6 @@ actual data class Profile(
attributes.remove("display_name")
}
}
- @Transient
var email: String?
get() = attributes["email"]
set(value) {
@@ -91,7 +85,6 @@ actual data class Profile(
attributes.remove("email")
}
}
- @Transient
var pictureUrl: String?
get() = attributes["picture_url"]
set(value) {
@@ -101,7 +94,6 @@ actual data class Profile(
attributes.remove("picture_url")
}
}
- @Transient
var profileUrl: String?
get() = attributes["profile_url"]
set(value) {
diff --git a/kvision-modules/kvision-server-spring-boot/build.gradle b/kvision-modules/kvision-server-spring-boot/build.gradle
index 6e79d007..e277547b 100644
--- a/kvision-modules/kvision-server-spring-boot/build.gradle
+++ b/kvision-modules/kvision-server-spring-boot/build.gradle
@@ -1,19 +1,29 @@
apply plugin: 'kotlin-platform-jvm'
apply plugin: 'kotlinx-serialization'
+repositories {
+ mavenCentral()
+ jcenter()
+ maven { url = "https://dl.bintray.com/kotlin/kotlin-eap" }
+ maven { url = 'https://kotlin.bintray.com/kotlinx' }
+ maven { url = 'https://dl.bintray.com/rjaros/kotlin' }
+ maven { url = "https://repo.spring.io/milestone" }
+}
+
dependencies {
expectedBy project(":kvision-modules:kvision-common-types")
expectedBy project(":kvision-modules:kvision-common-remote")
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
compile "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serializationVersion"
compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
+ compile "org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$coroutinesVersion"
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
compile "org.springframework.boot:spring-boot-starter:$springBootVersion"
- compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
- compile "org.springframework.boot:spring-boot-starter-websocket:$springBootVersion"
- compile "org.pac4j:pac4j-core:$pac4jVersion"
+ compile "org.springframework.boot:spring-boot-starter-webflux:$springBootVersion"
+ compile "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
+ compile "org.springframework.data:spring-data-relational:$springDataRelationalVersion"
compile "com.fasterxml.jackson.module:jackson-module-kotlin:${jacksonModuleKotlinVersion}"
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
testCompile project(":kvision-modules:kvision-common-types")
diff --git a/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/Annotations.kt b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/Annotations.kt
new file mode 100644
index 00000000..7655ae70
--- /dev/null
+++ b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/Annotations.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision.remote
+
+import org.springframework.data.annotation.Id
+import org.springframework.data.annotation.Transient
+import org.springframework.data.relational.core.mapping.Column
+import org.springframework.data.relational.core.mapping.Table
+
+actual typealias Id = Id
+
+actual typealias Transient = Transient
+
+actual typealias Table = Table
+
+actual typealias Column = Column
diff --git a/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVController.kt b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVController.kt
deleted file mode 100644
index 23e00284..00000000
--- a/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVController.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (c) 2017-present Robert Jaros
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-package pl.treksoft.kvision.remote
-
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.context.ApplicationContext
-import org.springframework.stereotype.Controller
-import org.springframework.web.bind.annotation.RequestMapping
-import org.springframework.web.bind.annotation.RequestMethod
-import javax.servlet.http.HttpServletRequest
-import javax.servlet.http.HttpServletResponse
-
-/**
- * Controller for handling automatic routes.
- */
-@Controller
-open class KVController {
-
- @Autowired
- lateinit var services: List<KVServiceManager<*>>
-
- @Autowired
- lateinit var applicationContext: ApplicationContext
-
- @RequestMapping(
- "/kv/**",
- method = [RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE, RequestMethod.OPTIONS]
- )
- open fun kVMapping(req: HttpServletRequest, res: HttpServletResponse) {
- val routeUrl = req.requestURI
- val handler = services.mapNotNull {
- when (req.method) {
- "GET" -> it.getRequests[routeUrl]
- "POST" -> it.postRequests[routeUrl]
- "PUT" -> it.putRequests[routeUrl]
- "DELETE" -> it.deleteRequests[routeUrl]
- "OPTIONS" -> it.optionsRequests[routeUrl]
- else -> null
- }
- }.firstOrNull()
- if (handler != null) {
- handler.invoke(req, res, applicationContext)
- } else {
- res.status = HttpServletResponse.SC_NOT_FOUND
- }
- }
-}
diff --git a/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVRouterConfiguration.kt b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVRouterConfiguration.kt
new file mode 100644
index 00000000..215848a7
--- /dev/null
+++ b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVRouterConfiguration.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision.remote
+
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.context.support.GenericApplicationContext
+import org.springframework.stereotype.Component
+import org.springframework.web.reactive.function.server.RouterFunction
+import org.springframework.web.reactive.function.server.ServerRequest
+import org.springframework.web.reactive.function.server.ServerResponse
+import org.springframework.web.reactive.function.server.buildAndAwait
+import org.springframework.web.reactive.function.server.coRouter
+import org.springframework.web.reactive.function.server.router
+import java.net.URI
+
+@Configuration
+open class KVRouterConfiguration {
+ @Bean
+ open fun kvRoutes(kvHandler: KVHandler) = coRouter {
+ GET("/kv/**", kvHandler::handle)
+ POST("/kv/**", kvHandler::handle)
+ PUT("/kv/**", kvHandler::handle)
+ DELETE("/kv/**", kvHandler::handle)
+ OPTIONS("/kv/**", kvHandler::handle)
+ }
+
+ @Bean
+ open fun indexRouter(): RouterFunction<ServerResponse> {
+ val redirectToIndex =
+ ServerResponse
+ .temporaryRedirect(URI("/index.html"))
+ .build()
+
+ return router {
+ GET("/") {
+ redirectToIndex
+ }
+ }
+ }
+}
+
+@Component
+open class KVHandler(var services: List<KVServiceManager<*>>, var applicationContext: ApplicationContext) {
+
+ open suspend fun handle(request: ServerRequest): ServerResponse {
+ val routeUrl = request.path()
+ val handler = services.mapNotNull {
+ when (request.method()?.name) {
+ "GET" -> it.getRequests[routeUrl]
+ "POST" -> it.postRequests[routeUrl]
+ "PUT" -> it.putRequests[routeUrl]
+ "DELETE" -> it.deleteRequests[routeUrl]
+ "OPTIONS" -> it.optionsRequests[routeUrl]
+ else -> null
+ }
+ }.firstOrNull()
+ return if (handler != null) {
+ handler(request, applicationContext as GenericApplicationContext)
+ } else {
+ ServerResponse.notFound().buildAndAwait()
+ }
+ }
+}
diff --git a/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt
index 185356da..14360abc 100644
--- a/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt
+++ b/kvision-modules/kvision-server-spring-boot/src/main/kotlin/pl/treksoft/kvision/remote/KVServiceManager.kt
@@ -21,24 +21,35 @@
*/
package pl.treksoft.kvision.remote
+import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
+import kotlinx.coroutines.reactive.awaitSingle
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.context.ApplicationContext
-import org.springframework.web.context.support.GenericWebApplicationContext
-import org.springframework.web.socket.WebSocketSession
-import javax.servlet.http.HttpServletRequest
-import javax.servlet.http.HttpServletResponse
+import org.springframework.security.core.Authentication
+import org.springframework.web.reactive.function.server.ServerRequest
+import org.springframework.web.reactive.function.server.ServerResponse
+import org.springframework.web.reactive.function.server.awaitBody
+import org.springframework.web.reactive.function.server.bodyValueAndAwait
+import org.springframework.web.reactive.function.server.json
+import org.springframework.web.reactive.socket.WebSocketSession
+import pl.treksoft.kvision.types.*
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.LocalTime
+import java.time.OffsetDateTime
+import java.time.OffsetTime
import kotlin.reflect.KClass
+
/**
* Multiplatform service manager for Spring Boot.
*/
@@ -50,25 +61,61 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass:
val LOG: Logger = LoggerFactory.getLogger(KVServiceManager::class.java.name)
}
- val getRequests: MutableMap<String, (HttpServletRequest, HttpServletResponse, ApplicationContext) -> Unit> =
+ val getRequests: MutableMap<String, suspend (ServerRequest, ApplicationContext) -> ServerResponse> =
mutableMapOf()
- val postRequests: MutableMap<String, (HttpServletRequest, HttpServletResponse, ApplicationContext) -> Unit> =
+ val postRequests: MutableMap<String, suspend (ServerRequest, ApplicationContext) -> ServerResponse> =
mutableMapOf()
- val putRequests: MutableMap<String, (HttpServletRequest, HttpServletResponse, ApplicationContext) -> Unit> =
+ val putRequests: MutableMap<String, suspend (ServerRequest, ApplicationContext) -> ServerResponse> =
mutableMapOf()
- val deleteRequests: MutableMap<String, (HttpServletRequest, HttpServletResponse, ApplicationContext) -> Unit> =
+ val deleteRequests: MutableMap<String, suspend (ServerRequest, ApplicationContext) -> ServerResponse> =
mutableMapOf()
- val optionsRequests: MutableMap<String, (HttpServletRequest, HttpServletResponse, ApplicationContext) -> Unit> =
+ val optionsRequests: MutableMap<String, suspend (ServerRequest, ApplicationContext) -> ServerResponse> =
mutableMapOf()
val webSocketsRequests: MutableMap<String, suspend (
- WebSocketSession, GenericWebApplicationContext, ReceiveChannel<String>, SendChannel<String>
+ WebSocketSession, ApplicationContext, ReceiveChannel<String>, SendChannel<String>
) -> Unit> =
mutableMapOf()
- val mapper = jacksonObjectMapper()
+ val mapper = jacksonObjectMapper().apply {
+ val module = SimpleModule()
+ module.addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer())
+ module.addSerializer(LocalDate::class.java, LocalDateSerializer())
+ module.addSerializer(LocalTime::class.java, LocalTimeSerializer())
+ module.addSerializer(OffsetDateTime::class.java, OffsetDateTimeSerializer())
+ module.addSerializer(OffsetTime::class.java, OffsetTimeSerializer())
+ module.addDeserializer(LocalDateTime::class.java, LocalDateTimeDeserializer())
+ module.addDeserializer(LocalDate::class.java, LocalDateDeserializer())
+ module.addDeserializer(LocalTime::class.java, LocalTimeDeserializer())
+ module.addDeserializer(OffsetDateTime::class.java, OffsetDateTimeDeserializer())
+ module.addDeserializer(OffsetTime::class.java, OffsetTimeDeserializer())
+ this.registerModule(module)
+ }
var counter: Int = 0
/**
+ * @suppress internal function
+ */
+ suspend fun initializeService(service: T, req: ServerRequest) {
+ if (service is WithRequest) {
+ service.serverRequest = req
+ }
+ if (service is WithWebSession) {
+ val session = req.session().awaitSingle()
+ service.webSession = session
+ }
+ if (service is WithPrincipal) {
+ val principal = req.principal().awaitSingle()
+ service.principal = principal
+ }
+ if (service is WithProfile) {
+ val profile = req.principal().ofType(Authentication::class.java).map {
+ it.principal as Profile
+ }.awaitSingle()
+ service.profile = profile
+ }
+ }
+
+ /**
* Binds a given route with a function of the receiver.
* @param function a function of the receiver
* @param method a HTTP method
@@ -80,35 +127,34 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass:
method: HttpMethod, route: String?
) {
val routeDef = route ?: "route${this::class.simpleName}${counter++}"
- addRoute(method, "/kv/$routeDef") { req, res, ctx ->
+ addRoute(method, "/kv/$routeDef") { req, ctx ->
val service = ctx.getBean(serviceClass.java)
+ initializeService(service, req)
val jsonRpcRequest = if (method == HttpMethod.GET) {
- JsonRpcRequest(req.getParameter("id")?.toInt() ?: 0, "", listOf())
+ JsonRpcRequest(req.queryParam("id").orElse(null)?.toInt() ?: 0, "", listOf())
} else {
- mapper.readValue(req.inputStream, JsonRpcRequest::class.java)
+ req.awaitBody()
}
- GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) {
- try {
- val result = function.invoke(service)
- res.writeJSON(
- mapper.writeValueAsString(
- JsonRpcResponse(
- id = jsonRpcRequest.id,
- result = mapper.writeValueAsString(result)
- )
+ try {
+ val result = function.invoke(service)
+ ServerResponse.ok().json().bodyValueAndAwait(
+ mapper.writeValueAsString(
+ JsonRpcResponse(
+ id = jsonRpcRequest.id,
+ result = mapper.writeValueAsString(result)
)
)
- } catch (e: Exception) {
- LOG.error(e.message, e)
- res.writeJSON(
- mapper.writeValueAsString(
- JsonRpcResponse(
- id = jsonRpcRequest.id,
- error = e.message ?: "Error"
- )
+ )
+ } catch (e: Exception) {
+ LOG.error(e.message, e)
+ ServerResponse.ok().json().bodyValueAndAwait(
+ mapper.writeValueAsString(
+ JsonRpcResponse(
+ id = jsonRpcRequest.id,
+ error = e.message ?: "Error"
)
)
- }
+ )
}
}
}
@@ -127,36 +173,35 @@ actual open class KVServiceManager<T : Any> actual constructor(val serviceClass:
if (method == HttpMethod.GET)
throw UnsupportedOperationException("GET method is only supported for methods without parameters")
val routeDef = route ?: "route${this::class.simpleName}${counter++}"
- addRoute(method, "/kv/$routeDef") { req, res, ctx ->
+ addRoute(method, "/kv/$routeDef") { req, ctx ->
val service = ctx.getBean(serviceClass.java)
- val jsonRpcRequest = mapper.readValue(req.inputStream, JsonRpcRequest::class.java)
+ initializeService(service, req)
+ val jsonRpcRequest = req.awaitBody<JsonRpcRequest>()
if (jsonRpcRequest.params.size == 1) {
val param = getParameter<PAR>(jsonRpcRequest.params[0])
- GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) {
- try {
- val result = function.invoke(service, param)
- res.writeJSON(
- mapper.writeValueAsString(
- JsonRpcResponse(
- id = jsonRpcRequest.id,
- result = mapper.writeValueAsString(result)
- )
+ try {
+ val result = function.invoke(service, param)
+ ServerResponse.ok().json().bodyValueAndAwait(
+ mapper.writeValueAsString(
+ JsonRpcResponse(
+ id = jsonRpcRequest.id,
+ result = mapper.writeValueAsString(result)
)
)
- } catch (e: Exception) {
- LOG.error(e.message, e)
- res.writeJSON(
- mapper.writeValueAsString(
- JsonRpcResponse(
- id = jsonRpcRequest.id,
- error = e.message ?: "Error"
- )
+ )
+ } catch (e: Exception) {
+ LOG.error(e.message,