aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/pl/treksoft/kvision/remote
diff options
context:
space:
mode:
authorRobert Jaros <rjaros@finn.pl>2018-04-26 15:41:32 +0200
committerRobert Jaros <rjaros@finn.pl>2018-04-26 15:41:32 +0200
commit95c1cdec5b0e65a0f617d5fa88d1d49a7abcfe95 (patch)
treeb3dc67083cad67d44a385605e4c915a0a05f47bb /src/main/kotlin/pl/treksoft/kvision/remote
parent5b9535e9964816eb228d3797f4c8a3e7676d1f53 (diff)
downloadkvision-95c1cdec5b0e65a0f617d5fa88d1d49a7abcfe95.tar.gz
kvision-95c1cdec5b0e65a0f617d5fa88d1d49a7abcfe95.tar.bz2
kvision-95c1cdec5b0e65a0f617d5fa88d1d49a7abcfe95.zip
Automatic JSON-RPC conectivity.
CallAgent component for remote JSON-RPC and AJAX.
Diffstat (limited to 'src/main/kotlin/pl/treksoft/kvision/remote')
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/remote/CallAgent.kt122
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/remote/RemoteAgent.kt114
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt45
3 files changed, 190 insertions, 91 deletions
diff --git a/src/main/kotlin/pl/treksoft/kvision/remote/CallAgent.kt b/src/main/kotlin/pl/treksoft/kvision/remote/CallAgent.kt
new file mode 100644
index 00000000..2f85a51f
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/remote/CallAgent.kt
@@ -0,0 +1,122 @@
+/*
+ * 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 kotlinx.serialization.json.JSON
+import pl.treksoft.jquery.JQueryAjaxSettings
+import pl.treksoft.jquery.JQueryXHR
+import pl.treksoft.jquery.jQuery
+import pl.treksoft.kvision.utils.obj
+import kotlin.js.Promise
+import kotlin.js.undefined
+import kotlin.js.JSON as NativeJSON
+
+/**
+ * An agent responsible for remote calls.
+ */
+open class CallAgent {
+
+ private var counter = 1
+
+ /**
+ * Makes an JSON-RPC call to the remote server.
+ * @param url an URL address
+ * @param method a HTTP method
+ * @param data data to be sent
+ * @return a promise of the result
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ fun jsonRpcCall(
+ url: String,
+ data: List<String?> = listOf(),
+ method: RpcHttpMethod = RpcHttpMethod.POST
+ ): Promise<String> {
+ val jsonRpcRequest = JsonRpcRequest(counter++, url, data)
+ val jsonData = JSON.stringify(jsonRpcRequest)
+ return Promise({ resolve, reject ->
+ jQuery.ajax(url, obj {
+ this.contentType = "application/json"
+ this.data = jsonData
+ this.method = method.name
+ this.success =
+ { data: dynamic, _: Any, _: Any ->
+ if (data.id != jsonRpcRequest.id) {
+ reject(Exception("Invalid response ID"))
+ } else if (data.error != null) {
+ reject(Exception(data.error.toString()))
+ } else if (data.result != null) {
+ resolve(data.result)
+ } else {
+ reject(Exception("Invalid response"))
+ }
+ }
+ this.error =
+ { xhr: JQueryXHR, _: String, errorText: String ->
+ val message = if (xhr.responseJSON != null && xhr.responseJSON != undefined) {
+ xhr.responseJSON.toString()
+ } else {
+ errorText
+ }
+ reject(Exception(message))
+ }
+ })
+ })
+ }
+
+ /**
+ * Makes a remote call to the remote server.
+ * @param url an URL address
+ * @param method a HTTP method
+ * @param data data to be sent
+ * @return a promise of the result
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ fun remoteCall(
+ url: String,
+ data: dynamic = null,
+ method: HttpMethod = HttpMethod.GET,
+ contentType: String = "application/json",
+ beforeSend: ((JQueryXHR, JQueryAjaxSettings) -> Boolean)? = null
+ ): Promise<dynamic> {
+ return Promise({ resolve, reject ->
+ jQuery.ajax(url, obj {
+ this.contentType = contentType
+ this.data = data
+ this.method = method.name
+ this.success =
+ { data: dynamic, _: Any, _: Any ->
+ resolve(data)
+ }
+ this.error =
+ { xhr: JQueryXHR, _: String, errorText: String ->
+ val message = if (xhr.responseJSON != null && xhr.responseJSON != undefined) {
+ xhr.responseJSON.toString()
+ } else {
+ errorText
+ }
+ reject(Exception(message))
+ }
+ this.beforeSend = beforeSend
+ })
+ })
+ }
+}
diff --git a/src/main/kotlin/pl/treksoft/kvision/remote/RemoteAgent.kt b/src/main/kotlin/pl/treksoft/kvision/remote/RemoteAgent.kt
index 3f7276f0..4c6ecf82 100644
--- a/src/main/kotlin/pl/treksoft/kvision/remote/RemoteAgent.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/remote/RemoteAgent.kt
@@ -32,30 +32,28 @@ import kotlinx.serialization.internal.StringSerializer
import kotlinx.serialization.json.JSON
import kotlinx.serialization.list
import kotlinx.serialization.serializer
-import pl.treksoft.jquery.JQueryXHR
-import pl.treksoft.jquery.jQuery
-import pl.treksoft.kvision.utils.obj
import kotlin.js.Promise
import kotlin.js.js
-import kotlin.js.undefined
import kotlin.reflect.KClass
import kotlin.js.JSON as NativeJSON
internal class NonStandardTypeException(type: String) : Exception("Non standard type: $type!")
/**
- * Client side agent for remote calls.
+ * Client side agent for JSON-RPC remote calls.
*/
@Suppress("EXPERIMENTAL_FEATURE_WARNING", "LargeClass", "TooManyFunctions")
open class RemoteAgent<out T>(val serviceManager: ServiceManager<T>) {
+ val callAgent = CallAgent()
+
/**
* Executes defined call to a remote web service.
*/
inline fun <reified RET : Any, T> call(noinline function: T.(Request?) -> Deferred<RET>): Promise<RET> {
- val url =
- serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
- return ajaxCall(url, null).then {
+ val (url, method) =
+ serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
+ return callAgent.jsonRpcCall(url, method = method).then {
try {
deserialize<RET>(it, RET::class.js.name)
} catch (t: NonStandardTypeException) {
@@ -68,9 +66,9 @@ open class RemoteAgent<out T>(val serviceManager: ServiceManager<T>) {
* Executes defined call to a remote web service.
*/
inline fun <reified RET : Any, T> call(noinline function: T.(Request?) -> Deferred<List<RET>>): Promise<List<RET>> {
- val url =
- serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
- return ajaxCall(url, null).then {
+ val (url, method) =
+ serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
+ return callAgent.jsonRpcCall(url, method = method).then {
try {
deserializeLists<RET>(it, RET::class.js.name)
} catch (t: NonStandardTypeException) {
@@ -87,9 +85,9 @@ open class RemoteAgent<out T>(val serviceManager: ServiceManager<T>) {
p: PAR, serializer: KSerializer<PAR>? = null
): Promise<RET> {
val data = serialize(p, serializer)
- val url =
- serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
- return ajaxCall(url, data).then {
+ val (url, method) =
+ serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
+ return callAgent.jsonRpcCall(url, listOf(data), method).then {
try {
@Suppress("UNCHECKED_CAST")
deserialize<RET>(it, (RET::class as KClass<Any>).js.name)
@@ -107,9 +105,9 @@ open class RemoteAgent<out T>(val serviceManager: ServiceManager<T>) {
p: PAR, serializer: KSerializer<PAR>? = null
): Promise<List<RET>> {
val data = serialize(p, serializer)
- val url =
- serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
- return ajaxCall(url, data).then {
+ val (url, method) =
+ serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
+ return callAgent.jsonRpcCall(url, listOf(data), method).then {
try {
deserializeLists<RET>(it, RET::class.js.name)
} catch (t: NonStandardTypeException) {
@@ -127,10 +125,9 @@ open class RemoteAgent<out T>(val serviceManager: ServiceManager<T>) {
): Promise<RET> {
val data1 = serialize(p1, serializer1)
val data2 = serialize(p2, serializer2)
- val data = "[ $data1 , $data2 ]"
- val url =
- serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
- return ajaxCall(url, data).then {
+ val (url, method) =
+ serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
+ return callAgent.jsonRpcCall(url, listOf(data1, data2), method).then {
try {
deserialize<RET>(it, RET::class.js.name)
} catch (t: NonStandardTypeException) {
@@ -148,10 +145,9 @@ open class RemoteAgent<out T>(val serviceManager: ServiceManager<T>) {
): Promise<List<RET>> {
val data1 = serialize(p1, serializer1)
val data2 = serialize(p2, serializer2)
- val data = "[ $data1 , $data2 ]"
- val url =
- serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
- return ajaxCall(url, data).then {
+ val (url, method) =
+ serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
+ return callAgent.jsonRpcCall(url, listOf(data1, data2), method).then {
try {
deserializeLists<RET>(it, RET::class.js.name)
} catch (t: NonStandardTypeException) {
@@ -171,10 +167,9 @@ open class RemoteAgent<out T>(val serviceManager: ServiceManager<T>) {
val data1 = serialize(p1, serializer1)
val data2 = serialize(p2, serializer2)
val data3 = serialize(p3, serializer3)
- val data = "[ $data1 , $data2 , $data3 ]"
- val url =
- serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
- return ajaxCall(url, data).then {
+ val (url, method) =
+ serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
+ return callAgent.jsonRpcCall(url, listOf(data1, data2, data3), method).then {
try {
deserialize<RET>(it, RET::class.js.name)
} catch (t: NonStandardTypeException) {
@@ -194,10 +189,9 @@ open class RemoteAgent<out T>(val serviceManager: ServiceManager<T>) {
val data1 = serialize(p1, serializer1)
val data2 = serialize(p2, serializer2)
val data3 = serialize(p3, serializer3)
- val data = "[ $data1 , $data2 , $data3 ]"
- val url =
- serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
- return ajaxCall(url, data).then {
+ val (url, method) =
+ serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
+ return callAgent.jsonRpcCall(url, listOf(data1, data2, data3), method).then {
try {
deserializeLists<RET>(it, RET::class.js.name)
} catch (t: NonStandardTypeException) {
@@ -224,10 +218,9 @@ open class RemoteAgent<out T>(val serviceManager: ServiceManager<T>) {
val data2 = serialize(p2, serializer2)
val data3 = serialize(p3, serializer3)
val data4 = serialize(p4, serializer4)
- val data = "[ $data1 , $data2 , $data3 , $data4 ]"
- val url =
- serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
- return ajaxCall(url, data).then {
+ val (url, method) =
+ serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
+ return callAgent.jsonRpcCall(url, listOf(data1, data2, data3, data4), method).then {
try {
deserialize<RET>(it, RET::class.js.name)
} catch (t: NonStandardTypeException) {
@@ -254,10 +247,9 @@ open class RemoteAgent<out T>(val serviceManager: ServiceManager<T>) {
val data2 = serialize(p2, serializer2)
val data3 = serialize(p3, serializer3)
val data4 = serialize(p4, serializer4)
- val data = "[ $data1 , $data2 , $data3 , $data4 ]"
- val url =
- serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
- return ajaxCall(url, data).then {
+ val (url, method) =
+ serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
+ return callAgent.jsonRpcCall(url, listOf(data1, data2, data3, data4), method).then {
try {
deserializeLists<RET>(it, RET::class.js.name)
} catch (t: NonStandardTypeException) {
@@ -289,10 +281,9 @@ open class RemoteAgent<out T>(val serviceManager: ServiceManager<T>) {
val data3 = serialize(p3, serializer3)
val data4 = serialize(p4, serializer4)
val data5 = serialize(p5, serializer5)
- val data = "[ $data1 , $data2 , $data3 , $data4 , $data5 ]"
- val url =
- serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
- return ajaxCall(url, data).then {
+ val (url, method) =
+ serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
+ return callAgent.jsonRpcCall(url, listOf(data1, data2, data3, data4, data5), method).then {
try {
deserialize<RET>(it, RET::class.js.name)
} catch (t: NonStandardTypeException) {
@@ -324,10 +315,9 @@ open class RemoteAgent<out T>(val serviceManager: ServiceManager<T>) {
val data3 = serialize(p3, serializer3)
val data4 = serialize(p4, serializer4)
val data5 = serialize(p5, serializer5)
- val data = "[ $data1 , $data2 , $data3 , $data4 , $data5 ]"
- val url =
- serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
- return ajaxCall(url, data).then {
+ val (url, method) =
+ serviceManager.getCalls()[function.toString()] ?: throw IllegalStateException("Function not specified!")
+ return callAgent.jsonRpcCall(url, listOf(data1, data2, data3, data4, data5), method).then {
try {
deserializeLists<RET>(it, RET::class.js.name)
} catch (t: NonStandardTypeException) {
@@ -336,32 +326,6 @@ open class RemoteAgent<out T>(val serviceManager: ServiceManager<T>) {
}
}
- /**
- * @suppress
- * Internal function
- */
- @Suppress("UnsafeCastFromDynamic")
- fun ajaxCall(url: String, data: Any?): Promise<String> =
- Promise({ resolve, reject ->
- jQuery.ajax(url, obj {
- this.contentType = "application/json"
- this.data = data
- this.method = "POST"
- this.success =
- { data: Any, _: Any, _: Any ->
- resolve(NativeJSON.stringify(data))
- }
- this.error =
- { xhr: JQueryXHR, _: String, errorText: String ->
- val message = if (xhr.responseJSON != null && xhr.responseJSON != undefined) {
- xhr.responseJSON.toString()
- } else {
- errorText
- }
- reject(Exception(message))
- }
- })
- })
/**
* @suppress
diff --git a/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt b/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt
index cd42f038..df3245c3 100644
--- a/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt
@@ -27,79 +27,93 @@ import kotlinx.coroutines.experimental.Deferred
* Multiplatform service manager.
*/
actual open class ServiceManager<out T> actual constructor(service: T?) {
- protected val calls: MutableMap<String, String> = mutableMapOf()
+ protected val calls: MutableMap<String, Pair<String, RpcHttpMethod>> = mutableMapOf()
/**
* Binds a given route with a function of the receiver.
* @param route a route
* @param function a function of the receiver
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
*/
protected actual inline fun <reified RET> bind(
route: String,
- noinline function: T.(Request?) -> Deferred<RET>
+ noinline function: T.(Request?) -> Deferred<RET>, method: RpcHttpMethod, prefix: String
) {
- calls[function.toString()] = "$SERVICE_PREFIX/$route"
+ calls[function.toString()] = Pair("$prefix$route", method)
}
/**
* Binds a given route with a function of the receiver.
* @param route a route
* @param function a function of the receiver
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
*/
protected actual inline fun <reified PAR, reified RET> bind(
route: String,
- noinline function: T.(PAR, Request?) -> Deferred<RET>
+ noinline function: T.(PAR, Request?) -> Deferred<RET>, method: RpcHttpMethod, prefix: String
) {
- calls[function.toString()] = "$SERVICE_PREFIX/$route"
+ calls[function.toString()] = Pair("$prefix$route", method)
}
/**
* Binds a given route with a function of the receiver.
* @param route a route
* @param function a function of the receiver
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
*/
protected actual inline fun <reified PAR1, reified PAR2, reified RET> bind(
route: String,
- noinline function: T.(PAR1, PAR2, Request?) -> Deferred<RET>
+ noinline function: T.(PAR1, PAR2, Request?) -> Deferred<RET>, method: RpcHttpMethod, prefix: String
) {
- calls[function.toString()] = "$SERVICE_PREFIX/$route"
+ calls[function.toString()] = Pair("$prefix$route", method)
}
/**
* Binds a given route with a function of the receiver.
* @param route a route
* @param function a function of the receiver
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
*/
protected actual inline fun <reified PAR1, reified PAR2, reified PAR3, reified RET> bind(
route: String,
- noinline function: T.(PAR1, PAR2, PAR3, Request?) -> Deferred<RET>
+ noinline function: T.(PAR1, PAR2, PAR3, Request?) -> Deferred<RET>, method: RpcHttpMethod, prefix: String
) {
- calls[function.toString()] = "$SERVICE_PREFIX/$route"
+ calls[function.toString()] = Pair("$prefix$route", method)
}
/**
* Binds a given route with a function of the receiver.
* @param route a route
* @param function a function of the receiver
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
*/
protected actual inline fun <reified PAR1, reified PAR2, reified PAR3, reified PAR4, reified RET> bind(
route: String,
- noinline function: T.(PAR1, PAR2, PAR3, PAR4, Request?) -> Deferred<RET>
+ noinline function: T.(PAR1, PAR2, PAR3, PAR4, Request?) -> Deferred<RET>, method: RpcHttpMethod, prefix: String
) {
- calls[function.toString()] = "$SERVICE_PREFIX/$route"
+ calls[function.toString()] = Pair("$prefix$route", method)
}
/**
* Binds a given route with a function of the receiver.
* @param route a route
* @param function a function of the receiver
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
*/
protected actual inline fun <reified PAR1, reified PAR2, reified PAR3,
reified PAR4, reified PAR5, reified RET> bind(
route: String,
- noinline function: T.(PAR1, PAR2, PAR3, PAR4, PAR5, Request?) -> Deferred<RET>
+ noinline function: T.(PAR1, PAR2, PAR3, PAR4, PAR5, Request?) -> Deferred<RET>,
+ method: RpcHttpMethod,
+ prefix: String
) {
- calls[function.toString()] = "$SERVICE_PREFIX/$route"
+ calls[function.toString()] = Pair("$prefix$route", method)
}
/**
@@ -110,8 +124,7 @@ actual open class ServiceManager<out T> actual constructor(service: T?) {
}
/**
- * Returns the list of defined bindings.
- * Not used on the jvm platform.
+ * Returns the map of defined paths.
*/
- actual fun getCalls(): Map<String, String> = calls
+ actual fun getCalls(): Map<String, Pair<String, RpcHttpMethod>> = calls
}