From 8820ae1aa038617fd669a51a4bc2513dab5c2f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kilczan?= Date: Thu, 16 May 2019 07:18:28 +0200 Subject: [PATCH 01/10] Rename ssml module to ssml-builder to fix an issue with the publish script --- settings.gradle | 2 +- spring-sample/build.gradle | 2 +- {ssml => ssml-builder}/build.gradle | 0 .../src/main/kotlin/org/rewedigital/dialog/ssml/AmazonEffect.kt | 0 .../src/main/kotlin/org/rewedigital/dialog/ssml/Emphasis.kt | 0 .../src/main/kotlin/org/rewedigital/dialog/ssml/InterpretAs.kt | 0 .../src/main/kotlin/org/rewedigital/dialog/ssml/SsmlBuilder.kt | 0 .../src/main/kotlin/org/rewedigital/dialog/ssml/Strength.kt | 0 .../src/main/kotlin/org/rewedigital/dialog/utils/RandomUtils.kt | 0 ssml-plugin/build.gradle | 2 +- 10 files changed, 3 insertions(+), 3 deletions(-) rename {ssml => ssml-builder}/build.gradle (100%) rename {ssml => ssml-builder}/src/main/kotlin/org/rewedigital/dialog/ssml/AmazonEffect.kt (100%) rename {ssml => ssml-builder}/src/main/kotlin/org/rewedigital/dialog/ssml/Emphasis.kt (100%) rename {ssml => ssml-builder}/src/main/kotlin/org/rewedigital/dialog/ssml/InterpretAs.kt (100%) rename {ssml => ssml-builder}/src/main/kotlin/org/rewedigital/dialog/ssml/SsmlBuilder.kt (100%) rename {ssml => ssml-builder}/src/main/kotlin/org/rewedigital/dialog/ssml/Strength.kt (100%) rename {ssml => ssml-builder}/src/main/kotlin/org/rewedigital/dialog/utils/RandomUtils.kt (100%) diff --git a/settings.gradle b/settings.gradle index 8dde0c3..0131776 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ rootProject.name = 'dialog' include 'core' -include 'ssml' +include 'ssml-builder' include 'ssml-plugin' include 'spring-sample' include 'spring-plugin' diff --git a/spring-sample/build.gradle b/spring-sample/build.gradle index 2cea9ca..de59ebd 100644 --- a/spring-sample/build.gradle +++ b/spring-sample/build.gradle @@ -38,7 +38,7 @@ dependencies { def askSdkVersion = '2.17.0' implementation project(':core') - implementation project(':ssml') + implementation project(':ssml-builder') implementation project(':ssml-plugin') implementation project(':spring-plugin') implementation project(':konversation-plugin') diff --git a/ssml/build.gradle b/ssml-builder/build.gradle similarity index 100% rename from ssml/build.gradle rename to ssml-builder/build.gradle diff --git a/ssml/src/main/kotlin/org/rewedigital/dialog/ssml/AmazonEffect.kt b/ssml-builder/src/main/kotlin/org/rewedigital/dialog/ssml/AmazonEffect.kt similarity index 100% rename from ssml/src/main/kotlin/org/rewedigital/dialog/ssml/AmazonEffect.kt rename to ssml-builder/src/main/kotlin/org/rewedigital/dialog/ssml/AmazonEffect.kt diff --git a/ssml/src/main/kotlin/org/rewedigital/dialog/ssml/Emphasis.kt b/ssml-builder/src/main/kotlin/org/rewedigital/dialog/ssml/Emphasis.kt similarity index 100% rename from ssml/src/main/kotlin/org/rewedigital/dialog/ssml/Emphasis.kt rename to ssml-builder/src/main/kotlin/org/rewedigital/dialog/ssml/Emphasis.kt diff --git a/ssml/src/main/kotlin/org/rewedigital/dialog/ssml/InterpretAs.kt b/ssml-builder/src/main/kotlin/org/rewedigital/dialog/ssml/InterpretAs.kt similarity index 100% rename from ssml/src/main/kotlin/org/rewedigital/dialog/ssml/InterpretAs.kt rename to ssml-builder/src/main/kotlin/org/rewedigital/dialog/ssml/InterpretAs.kt diff --git a/ssml/src/main/kotlin/org/rewedigital/dialog/ssml/SsmlBuilder.kt b/ssml-builder/src/main/kotlin/org/rewedigital/dialog/ssml/SsmlBuilder.kt similarity index 100% rename from ssml/src/main/kotlin/org/rewedigital/dialog/ssml/SsmlBuilder.kt rename to ssml-builder/src/main/kotlin/org/rewedigital/dialog/ssml/SsmlBuilder.kt diff --git a/ssml/src/main/kotlin/org/rewedigital/dialog/ssml/Strength.kt b/ssml-builder/src/main/kotlin/org/rewedigital/dialog/ssml/Strength.kt similarity index 100% rename from ssml/src/main/kotlin/org/rewedigital/dialog/ssml/Strength.kt rename to ssml-builder/src/main/kotlin/org/rewedigital/dialog/ssml/Strength.kt diff --git a/ssml/src/main/kotlin/org/rewedigital/dialog/utils/RandomUtils.kt b/ssml-builder/src/main/kotlin/org/rewedigital/dialog/utils/RandomUtils.kt similarity index 100% rename from ssml/src/main/kotlin/org/rewedigital/dialog/utils/RandomUtils.kt rename to ssml-builder/src/main/kotlin/org/rewedigital/dialog/utils/RandomUtils.kt diff --git a/ssml-plugin/build.gradle b/ssml-plugin/build.gradle index 5070eab..dad1188 100644 --- a/ssml-plugin/build.gradle +++ b/ssml-plugin/build.gradle @@ -10,7 +10,7 @@ apply from: '../docu.gradle' dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation project(':core') - implementation project(':ssml') + implementation project(':ssml-builder') } dokka { From 7cf53fda71cd4e541caef03b072a9cd0d21f8b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kilczan?= Date: Thu, 11 Jul 2019 12:20:59 +0200 Subject: [PATCH 02/10] Add helper class to manipulate the user storage and use the storage for the user id generation --- .../dialog/handler/DialogflowHandler.kt | 9 +-- .../dialog/model/google/GooglePayload.kt | 4 +- .../rewedigital/dialog/model/google/User.kt | 5 +- .../dialog/model/google/UserStorageHolder.kt | 74 +++++++++++++++++++ .../dialog/model/google/GooglePayloadTest.kt | 43 +++++++++++ 5 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorageHolder.kt create mode 100644 spring-sample/src/test/kotlin/org/rewedigital/dialog/model/google/GooglePayloadTest.kt diff --git a/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt b/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt index 0b0f7b8..6ef1516 100644 --- a/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt +++ b/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt @@ -1,14 +1,12 @@ package org.rewedigital.dialog.handler -import org.rewedigital.dialog.extensions.getSelectedOption -import org.rewedigital.dialog.extensions.hasSurfaceCapability -import org.rewedigital.dialog.extensions.permissionGranted -import org.rewedigital.dialog.extensions.signInStatus +import org.rewedigital.dialog.extensions.* import org.rewedigital.dialog.model.dialogflow.DialogflowParams import org.rewedigital.dialog.model.dialogflow.OutputContext import org.rewedigital.dialog.model.dialogflow.WebhookRequest import org.rewedigital.dialog.model.google.Conversation import org.rewedigital.dialog.model.google.SurfaceCapabilities +import org.rewedigital.dialog.model.google.userData /** * Wrapper of [WebhookRequest] which provides utility functions for easier context access and other parameters. @@ -53,8 +51,7 @@ class DialogflowHandler(private val webhookRequest: WebhookRequest) { * An unique identifier of the users google account. */ val userId: String? - get() = webhookRequest.originalDetectIntentRequest?.payload?.user?.userId - ?: webhookRequest.originalDetectIntentRequest?.payload?.user?.idToken + get() = webhookRequest.originalDetectIntentRequest?.payload?.user?.userData?.userId /** * The unique identifier of detectIntent request session. diff --git a/core/src/main/kotlin/org/rewedigital/dialog/model/google/GooglePayload.kt b/core/src/main/kotlin/org/rewedigital/dialog/model/google/GooglePayload.kt index 31ed689..884d16e 100644 --- a/core/src/main/kotlin/org/rewedigital/dialog/model/google/GooglePayload.kt +++ b/core/src/main/kotlin/org/rewedigital/dialog/model/google/GooglePayload.kt @@ -7,6 +7,6 @@ data class GooglePayload( var permissionsRequest: PermissionsRequest? = null, var systemIntent: SystemIntent? = null, var richResponse: RichResponse? = null, - var userStorage: String? = null, + override var userStorage: String? = null, var noInputPrompts: MutableList = mutableListOf() -) \ No newline at end of file +) : UserStorageHolder \ No newline at end of file diff --git a/core/src/main/kotlin/org/rewedigital/dialog/model/google/User.kt b/core/src/main/kotlin/org/rewedigital/dialog/model/google/User.kt index bba3dc6..2749b02 100644 --- a/core/src/main/kotlin/org/rewedigital/dialog/model/google/User.kt +++ b/core/src/main/kotlin/org/rewedigital/dialog/model/google/User.kt @@ -2,15 +2,14 @@ package org.rewedigital.dialog.model.google data class User( - @Deprecated("This item is deprecated!") val userId: String?, val idToken: String?, val profile: Profile?, val accessToken: String?, val permissions: List?, val locale: String?, val lastSeen: String?, - val userStorage: String? -) { + override var userStorage: String? +) : UserStorageHolder { data class Profile( val displayName: String?, val givenName: String?, diff --git a/core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorageHolder.kt b/core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorageHolder.kt new file mode 100644 index 0000000..b70769e --- /dev/null +++ b/core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorageHolder.kt @@ -0,0 +1,74 @@ +package org.rewedigital.dialog.model.google + +import java.util.* + +interface UserStorageHolder { + var userStorage: String? +} + +val UserStorageHolder.userData: DataStorage +get() = DataStorage(this) + +class DataStorage(private val userData : UserStorageHolder) : LinkedHashMap() { + init { + val data = userData.userStorage + data?.let { + val dataPos = data.indexOf("data") + val firstBracket = data.indexOf("{") + val secondBracket = data.indexOf("{", 1) + val seemsValid = + firstBracket == 0 && dataPos > firstBracket && secondBracket > dataPos && data.endsWith("}}") + if (seemsValid) { + // remove known pre- and suffix + data.substring(secondBracket + 1, data.length - 2) + .split(',') + .forEach { keyValuePair -> + val (key, value) = keyValuePair.split(":") + put(key.stripQuotesAndSlashes(), value.stripQuotesAndSlashes()) + } + } + } + } + + private fun String.stripQuotesAndSlashes(): String { + return trim().trim('"').replace("\\\\", "\\") + } + + override fun clear() { + super.clear() + userData.userStorage = asJson() + } + + override fun putAll(from: Map) { + super.putAll(from) + userData.userStorage = asJson() + } + + override fun put(key: String, value: String) = + super.put(key, value).also { + userData.userStorage = asJson() + } + + override fun remove(key: String) = + super.remove(key).also { + userData.userStorage = asJson() + } + + override fun remove(key: String, value: String)= + super.remove(key, value).also { + userData.userStorage = asJson() + } + + val userId: String + get() = getOrPut("userId") { UUID.randomUUID().toString() } + + private fun asJson(): String? = + if (entries.size > 0) + entries + .sortedBy { it.key } // order by key to make the output more deterministic + .joinToString(separator = ",", prefix = """{"data":{""", postfix = "}}") { + fun String.mask() = replace("\"", "\\\"").replace("\\", "\\\\") + """"${it.key.mask()}":"${it.value.mask()}"""" + } + else null +} \ No newline at end of file diff --git a/spring-sample/src/test/kotlin/org/rewedigital/dialog/model/google/GooglePayloadTest.kt b/spring-sample/src/test/kotlin/org/rewedigital/dialog/model/google/GooglePayloadTest.kt new file mode 100644 index 0000000..0426b89 --- /dev/null +++ b/spring-sample/src/test/kotlin/org/rewedigital/dialog/model/google/GooglePayloadTest.kt @@ -0,0 +1,43 @@ +package org.rewedigital.dialog.model.google + +import org.junit.Assert.* +import org.junit.Test + +class GooglePayloadTest { + @Test + fun `check implicit user id generation`() { + val sut = GooglePayload() + assertNull(sut.userStorage) + assertNotNull(sut.userData.userId) + assertNotNull(sut.userStorage) + } + + @Test + fun `check user id generation in output`() { + val sut = GooglePayload() + val userId = sut.userData.userId + assertNotNull(userId) + assertEquals("userId should not change",userId, sut.userData.userId) + assertEquals("userStorage has unexpected content", """{"data":{"userId":"$userId"}}""", sut.userStorage) + } + + @Test + fun `check custom payload`() { + val sut = GooglePayload() + sut.userData["Hello"] = "World" + assertEquals("userStorage has unexpected content", """{"data":{"Hello":"World"}}""", sut.userStorage) + } + + @Test + fun `check custom payload with old user id`() { + val sut = GooglePayload(userStorage = """{"data":{"userId":"abcdef"}}""") + sut.userData["Hello"] = "World" + assertEquals("userStorage has unexpected content", """{"data":{"Hello":"World","userId":"abcdef"}}""", sut.userStorage) + } + + @Test + fun `check parsing of user id`() { + val sut = GooglePayload(userStorage = """{"data":{"userId":"abcdef"}}""") + assertEquals("unexpected user id", "abcdef", sut.userData.userId) + } +} \ No newline at end of file From b173d5d61e8b9f98e79e345e7dae9d5083d68416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kilczan?= Date: Thu, 11 Jul 2019 12:21:20 +0200 Subject: [PATCH 03/10] Fix warning --- .../org/rewedigital/dialog/handler/DialogflowHandler.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt b/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt index 6ef1516..7bf47a6 100644 --- a/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt +++ b/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt @@ -17,8 +17,8 @@ class DialogflowHandler(private val webhookRequest: WebhookRequest) { /** * Holds context related information. */ - val context: DialogflowHandler.ContextWrapper = - DialogflowHandler.ContextWrapper(webhookRequest.queryResult?.outputContexts.orEmpty().map { + val context: ContextWrapper = + ContextWrapper(webhookRequest.queryResult?.outputContexts.orEmpty().map { // clone the list and remove the session prefix it.copy(name = it.name?.replace("${webhookRequest.session.orEmpty()}/contexts/", "")) }.toMutableList(), webhookRequest.session.orEmpty()) From a75dcda0b6afab5a7a401ceb50c7428f69315451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kilczan?= Date: Thu, 11 Jul 2019 12:37:20 +0200 Subject: [PATCH 04/10] Playing with version numbers --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5a2435c..65f9aea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ cache: - $HOME/.gradle/wrapper/ jdk: - - oraclejdk8 + - oraclejdk10 after_success: - bash <(curl -s https://codecov.io/bash) \ No newline at end of file From ab8da2c469a24654756240f4f9c48474d85a40dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kilczan?= Date: Thu, 11 Jul 2019 12:38:59 +0200 Subject: [PATCH 05/10] Try using Open JDK 10 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 65f9aea..21a27c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ cache: - $HOME/.gradle/wrapper/ jdk: - - oraclejdk10 + - openjdk10 after_success: - bash <(curl -s https://codecov.io/bash) \ No newline at end of file From fde6d695118f3f4d2190f2d52429378838605b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kilczan?= Date: Thu, 11 Jul 2019 12:59:00 +0200 Subject: [PATCH 06/10] Bump Kotlin and library version --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 51e0d43..d8e1e2b 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,9 @@ apply plugin: 'com.github.ben-manes.versions' buildscript { ext.versions = [:] - ext.versions.kotlin = '1.3.30' + ext.versions.kotlin = '1.3.31' ext.versions.dokka = '0.9.18' - ext.versions.core = '1.0.1' + ext.versions.core = '1.0.2' ext.versions.alexa = '1.0.1' ext.versions.ssml = '1.0.2' ext.versions.ssml_plugin = '1.0.2' From 8b1e46670a9122173264ef1553dfb5b3968c9494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kilczan?= Date: Thu, 11 Jul 2019 14:24:50 +0200 Subject: [PATCH 07/10] Connect the input user storage with the output user storage --- .../rewedigital/dialog/handler/DialogflowHandler.kt | 11 ++++++++++- .../dialog/handler/DialogflowResponseBuilder.kt | 2 +- .../org/rewedigital/dialog/model/google/User.kt | 1 + .../dialog/model/google/UserStorageHolder.kt | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt b/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt index 7bf47a6..41a6aa2 100644 --- a/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt +++ b/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt @@ -5,6 +5,7 @@ import org.rewedigital.dialog.model.dialogflow.DialogflowParams import org.rewedigital.dialog.model.dialogflow.OutputContext import org.rewedigital.dialog.model.dialogflow.WebhookRequest import org.rewedigital.dialog.model.google.Conversation +import org.rewedigital.dialog.model.google.DataStorage import org.rewedigital.dialog.model.google.SurfaceCapabilities import org.rewedigital.dialog.model.google.userData @@ -51,7 +52,15 @@ class DialogflowHandler(private val webhookRequest: WebhookRequest) { * An unique identifier of the users google account. */ val userId: String? - get() = webhookRequest.originalDetectIntentRequest?.payload?.user?.userData?.userId + get() = webhookRequest.originalDetectIntentRequest?.payload?.user?.userId + ?: webhookRequest.originalDetectIntentRequest?.payload?.user?.userData?.userId + + /** + * The stored user data aka user storage. + * @see https://developers.google.com/actions/assistant/save-data#json + */ + val userData: DataStorage? + get() = webhookRequest.originalDetectIntentRequest?.payload?.user?.userData /** * The unique identifier of detectIntent request session. diff --git a/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowResponseBuilder.kt b/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowResponseBuilder.kt index fe1abba..49c9dc6 100644 --- a/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowResponseBuilder.kt +++ b/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowResponseBuilder.kt @@ -11,7 +11,7 @@ class DialogflowResponseBuilder(private val dialogflowHandler: DialogflowHandler private val response = WebhookResponse() private fun WebhookResponse.getOrCreatePayload() = - payload ?: Payload(google = GooglePayload(richResponse = RichResponse())).also { newPayload -> + payload ?: Payload(google = GooglePayload(richResponse = RichResponse(), userStorage = dialogflowHandler.userData?.asJson())).also { newPayload -> payload = newPayload } diff --git a/core/src/main/kotlin/org/rewedigital/dialog/model/google/User.kt b/core/src/main/kotlin/org/rewedigital/dialog/model/google/User.kt index 2749b02..2268008 100644 --- a/core/src/main/kotlin/org/rewedigital/dialog/model/google/User.kt +++ b/core/src/main/kotlin/org/rewedigital/dialog/model/google/User.kt @@ -2,6 +2,7 @@ package org.rewedigital.dialog.model.google data class User( + @Deprecated("This item is deprecated!") val userId: String?, val idToken: String?, val profile: Profile?, val accessToken: String?, diff --git a/core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorageHolder.kt b/core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorageHolder.kt index b70769e..e61e495 100644 --- a/core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorageHolder.kt +++ b/core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorageHolder.kt @@ -62,7 +62,7 @@ class DataStorage(private val userData : UserStorageHolder) : LinkedHashMap 0) entries .sortedBy { it.key } // order by key to make the output more deterministic From 049a23b93c92a77d999a843382ae7e9da2ae8a01 Mon Sep 17 00:00:00 2001 From: Volkmar Vogel Date: Fri, 12 Jul 2019 15:19:10 +0200 Subject: [PATCH 08/10] migrate userId to custom generated userId witch is saved in the userData --- core/build.gradle | 1 + .../dialog/handler/DialogflowHandler.kt | 62 +++++++++++++--- .../handler/DialogflowResponseBuilder.kt | 17 ++++- .../dialog/model/google/GooglePayload.kt | 4 +- .../rewedigital/dialog/model/google/User.kt | 4 +- .../dialog/model/google/UserStorage.kt | 6 -- .../dialog/model/google/UserStorageHolder.kt | 74 ------------------- .../dialog/model/google/GooglePayloadTest.kt | 43 ----------- 8 files changed, 71 insertions(+), 140 deletions(-) delete mode 100644 core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorage.kt delete mode 100644 core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorageHolder.kt delete mode 100644 spring-sample/src/test/kotlin/org/rewedigital/dialog/model/google/GooglePayloadTest.kt diff --git a/core/build.gradle b/core/build.gradle index 3a8f66a..ca33924 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -7,6 +7,7 @@ description 'Dialog is a Dialogflow v2 API implementation written in Kotlin. Wit dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + implementation 'com.google.code.gson:gson:2.8.5' } task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) { diff --git a/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt b/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt index 41a6aa2..077a955 100644 --- a/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt +++ b/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowHandler.kt @@ -1,13 +1,15 @@ package org.rewedigital.dialog.handler +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken import org.rewedigital.dialog.extensions.* import org.rewedigital.dialog.model.dialogflow.DialogflowParams import org.rewedigital.dialog.model.dialogflow.OutputContext import org.rewedigital.dialog.model.dialogflow.WebhookRequest import org.rewedigital.dialog.model.google.Conversation -import org.rewedigital.dialog.model.google.DataStorage import org.rewedigital.dialog.model.google.SurfaceCapabilities -import org.rewedigital.dialog.model.google.userData +import java.util.* +import kotlin.collections.HashMap /** * Wrapper of [WebhookRequest] which provides utility functions for easier context access and other parameters. @@ -24,6 +26,16 @@ class DialogflowHandler(private val webhookRequest: WebhookRequest) { it.copy(name = it.name?.replace("${webhookRequest.session.orEmpty()}/contexts/", "")) }.toMutableList(), webhookRequest.session.orEmpty()) + /** + * The stored user data aka user storage. + * @see https://developers.google.com/actions/assistant/save-data#json + */ + val userData: MutableMap = run { + val userData = webhookRequest.originalDetectIntentRequest?.payload?.user?.userStorage + ?: return@run mutableMapOf() + fromJson(userData).toMutableMap() + } + /** * The action name defined in Dialogflow. */ @@ -51,16 +63,22 @@ class DialogflowHandler(private val webhookRequest: WebhookRequest) { /** * An unique identifier of the users google account. */ - val userId: String? - get() = webhookRequest.originalDetectIntentRequest?.payload?.user?.userId - ?: webhookRequest.originalDetectIntentRequest?.payload?.user?.userData?.userId - - /** - * The stored user data aka user storage. - * @see https://developers.google.com/actions/assistant/save-data#json - */ - val userData: DataStorage? - get() = webhookRequest.originalDetectIntentRequest?.payload?.user?.userData + val userId: String + get() { + return if (userData.containsKey("userId")) { + userData["userId"] as String + } else { + val oldUserId = webhookRequest.originalDetectIntentRequest?.payload?.user?.userId + if (oldUserId.isNullOrEmpty()) { + val newUserId = UUID.randomUUID().toString() + userData["userId"] = newUserId + newUserId + } else { + userData["userId"] = oldUserId + oldUserId + } + } + } /** * The unique identifier of detectIntent request session. @@ -188,6 +206,26 @@ class DialogflowHandler(private val webhookRequest: WebhookRequest) { context[name, key] = value } + private fun fromJson(serializedValue: String?): Map { + if (serializedValue != null && serializedValue.isNotEmpty()) { + val gson = Gson() + try { + val map: Map = gson.fromJson( + serializedValue, + object : TypeToken>() {}.type + ) + // NOTE: The format of the opaque string is: + // keyValueData: {key:value; key:value; } + if (map["data"] != null) { + return map["data"] as Map + } + } catch (e: Exception) { + println(e.message) + } + } + return HashMap() + } + class ContextWrapper( private val context: MutableList, private val session: String diff --git a/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowResponseBuilder.kt b/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowResponseBuilder.kt index 49c9dc6..1e734bc 100644 --- a/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowResponseBuilder.kt +++ b/core/src/main/kotlin/org/rewedigital/dialog/handler/DialogflowResponseBuilder.kt @@ -1,5 +1,6 @@ package org.rewedigital.dialog.handler +import com.google.gson.Gson import org.rewedigital.dialog.extensions.validate import org.rewedigital.dialog.factories.SystemIntentFactory import org.rewedigital.dialog.model.dialogflow.* @@ -11,7 +12,11 @@ class DialogflowResponseBuilder(private val dialogflowHandler: DialogflowHandler private val response = WebhookResponse() private fun WebhookResponse.getOrCreatePayload() = - payload ?: Payload(google = GooglePayload(richResponse = RichResponse(), userStorage = dialogflowHandler.userData?.asJson())).also { newPayload -> + payload ?: Payload( + google = GooglePayload( + richResponse = RichResponse() + ) + ).also { newPayload -> payload = newPayload } @@ -262,6 +267,16 @@ class DialogflowResponseBuilder(private val dialogflowHandler: DialogflowHandler .apply { source = "Webhook" outputContexts = dialogflowHandler.getContextList().toMutableList() + getOrCreatePayload().google?.userStorage = run { + val dataMap = HashMap() + dataMap["data"] = dialogflowHandler.userData + Gson().toJson(dataMap) + } + + // Remove RichResponse if there are no items in it + if (payload?.google?.richResponse?.items?.isEmpty() == true) { + payload?.google?.richResponse = null + } } .validate() } \ No newline at end of file diff --git a/core/src/main/kotlin/org/rewedigital/dialog/model/google/GooglePayload.kt b/core/src/main/kotlin/org/rewedigital/dialog/model/google/GooglePayload.kt index 884d16e..31ed689 100644 --- a/core/src/main/kotlin/org/rewedigital/dialog/model/google/GooglePayload.kt +++ b/core/src/main/kotlin/org/rewedigital/dialog/model/google/GooglePayload.kt @@ -7,6 +7,6 @@ data class GooglePayload( var permissionsRequest: PermissionsRequest? = null, var systemIntent: SystemIntent? = null, var richResponse: RichResponse? = null, - override var userStorage: String? = null, + var userStorage: String? = null, var noInputPrompts: MutableList = mutableListOf() -) : UserStorageHolder \ No newline at end of file +) \ No newline at end of file diff --git a/core/src/main/kotlin/org/rewedigital/dialog/model/google/User.kt b/core/src/main/kotlin/org/rewedigital/dialog/model/google/User.kt index 2268008..0d838e1 100644 --- a/core/src/main/kotlin/org/rewedigital/dialog/model/google/User.kt +++ b/core/src/main/kotlin/org/rewedigital/dialog/model/google/User.kt @@ -9,8 +9,8 @@ data class User( val permissions: List?, val locale: String?, val lastSeen: String?, - override var userStorage: String? -) : UserStorageHolder { + var userStorage: String? +) { data class Profile( val displayName: String?, val givenName: String?, diff --git a/core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorage.kt b/core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorage.kt deleted file mode 100644 index a21bc6a..0000000 --- a/core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorage.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.rewedigital.dialog.model.google - -import org.rewedigital.dialog.model.dialogflow.DialogflowParams - - -data class UserStorage(val data: DialogflowParams) \ No newline at end of file diff --git a/core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorageHolder.kt b/core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorageHolder.kt deleted file mode 100644 index e61e495..0000000 --- a/core/src/main/kotlin/org/rewedigital/dialog/model/google/UserStorageHolder.kt +++ /dev/null @@ -1,74 +0,0 @@ -package org.rewedigital.dialog.model.google - -import java.util.* - -interface UserStorageHolder { - var userStorage: String? -} - -val UserStorageHolder.userData: DataStorage -get() = DataStorage(this) - -class DataStorage(private val userData : UserStorageHolder) : LinkedHashMap() { - init { - val data = userData.userStorage - data?.let { - val dataPos = data.indexOf("data") - val firstBracket = data.indexOf("{") - val secondBracket = data.indexOf("{", 1) - val seemsValid = - firstBracket == 0 && dataPos > firstBracket && secondBracket > dataPos && data.endsWith("}}") - if (seemsValid) { - // remove known pre- and suffix - data.substring(secondBracket + 1, data.length - 2) - .split(',') - .forEach { keyValuePair -> - val (key, value) = keyValuePair.split(":") - put(key.stripQuotesAndSlashes(), value.stripQuotesAndSlashes()) - } - } - } - } - - private fun String.stripQuotesAndSlashes(): String { - return trim().trim('"').replace("\\\\", "\\") - } - - override fun clear() { - super.clear() - userData.userStorage = asJson() - } - - override fun putAll(from: Map) { - super.putAll(from) - userData.userStorage = asJson() - } - - override fun put(key: String, value: String) = - super.put(key, value).also { - userData.userStorage = asJson() - } - - override fun remove(key: String) = - super.remove(key).also { - userData.userStorage = asJson() - } - - override fun remove(key: String, value: String)= - super.remove(key, value).also { - userData.userStorage = asJson() - } - - val userId: String - get() = getOrPut("userId") { UUID.randomUUID().toString() } - - fun asJson(): String? = - if (entries.size > 0) - entries - .sortedBy { it.key } // order by key to make the output more deterministic - .joinToString(separator = ",", prefix = """{"data":{""", postfix = "}}") { - fun String.mask() = replace("\"", "\\\"").replace("\\", "\\\\") - """"${it.key.mask()}":"${it.value.mask()}"""" - } - else null -} \ No newline at end of file diff --git a/spring-sample/src/test/kotlin/org/rewedigital/dialog/model/google/GooglePayloadTest.kt b/spring-sample/src/test/kotlin/org/rewedigital/dialog/model/google/GooglePayloadTest.kt deleted file mode 100644 index 0426b89..0000000 --- a/spring-sample/src/test/kotlin/org/rewedigital/dialog/model/google/GooglePayloadTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.rewedigital.dialog.model.google - -import org.junit.Assert.* -import org.junit.Test - -class GooglePayloadTest { - @Test - fun `check implicit user id generation`() { - val sut = GooglePayload() - assertNull(sut.userStorage) - assertNotNull(sut.userData.userId) - assertNotNull(sut.userStorage) - } - - @Test - fun `check user id generation in output`() { - val sut = GooglePayload() - val userId = sut.userData.userId - assertNotNull(userId) - assertEquals("userId should not change",userId, sut.userData.userId) - assertEquals("userStorage has unexpected content", """{"data":{"userId":"$userId"}}""", sut.userStorage) - } - - @Test - fun `check custom payload`() { - val sut = GooglePayload() - sut.userData["Hello"] = "World" - assertEquals("userStorage has unexpected content", """{"data":{"Hello":"World"}}""", sut.userStorage) - } - - @Test - fun `check custom payload with old user id`() { - val sut = GooglePayload(userStorage = """{"data":{"userId":"abcdef"}}""") - sut.userData["Hello"] = "World" - assertEquals("userStorage has unexpected content", """{"data":{"Hello":"World","userId":"abcdef"}}""", sut.userStorage) - } - - @Test - fun `check parsing of user id`() { - val sut = GooglePayload(userStorage = """{"data":{"userId":"abcdef"}}""") - assertEquals("unexpected user id", "abcdef", sut.userData.userId) - } -} \ No newline at end of file From a7f289817157c972d479b802ddb354c71e324308 Mon Sep 17 00:00:00 2001 From: Volkmar Vogel Date: Fri, 12 Jul 2019 15:30:56 +0200 Subject: [PATCH 09/10] update kotlin version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d8e1e2b..d1ce114 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'com.github.ben-manes.versions' buildscript { ext.versions = [:] - ext.versions.kotlin = '1.3.31' + ext.versions.kotlin = '1.3.41' ext.versions.dokka = '0.9.18' ext.versions.core = '1.0.2' ext.versions.alexa = '1.0.1' From 9f12ed0c0464eba60edfb6c174db361ce41d6022 Mon Sep 17 00:00:00 2001 From: Volkmar Vogel Date: Fri, 12 Jul 2019 15:37:07 +0200 Subject: [PATCH 10/10] update dependencies --- alexa-plugin/build.gradle | 2 +- alexa-spring-plugin/build.gradle | 4 ++-- spring-plugin/build.gradle | 2 +- spring-sample/build.gradle | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/alexa-plugin/build.gradle b/alexa-plugin/build.gradle index 4462813..865f077 100644 --- a/alexa-plugin/build.gradle +++ b/alexa-plugin/build.gradle @@ -8,7 +8,7 @@ description 'The Alexa plugin for Dialog to write voice applications for Dialogf dependencies { implementation project(":core") implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - implementation "com.amazon.alexa:ask-sdk-core:2.17.0" + implementation "com.amazon.alexa:ask-sdk-core:2.19.0" } dokka { diff --git a/alexa-spring-plugin/build.gradle b/alexa-spring-plugin/build.gradle index e67fdd4..db17552 100644 --- a/alexa-spring-plugin/build.gradle +++ b/alexa-spring-plugin/build.gradle @@ -22,8 +22,8 @@ dependencies { implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' implementation 'org.jetbrains.kotlin:kotlin-reflect' - implementation 'org.springframework:spring-context:5.1.4.RELEASE' - implementation 'com.amazon.alexa:ask-sdk-core:2.11.2' + implementation 'org.springframework:spring-context:5.1.8.RELEASE' + implementation 'com.amazon.alexa:ask-sdk-core:2.19.0' } task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) { diff --git a/spring-plugin/build.gradle b/spring-plugin/build.gradle index 07301e6..f9266e4 100644 --- a/spring-plugin/build.gradle +++ b/spring-plugin/build.gradle @@ -20,7 +20,7 @@ dependencies { implementation 'org.jetbrains.kotlin:kotlin-reflect' implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' - implementation 'org.springframework:spring-context:5.1.6.RELEASE' + implementation 'org.springframework:spring-context:5.1.8.RELEASE' } dokka { diff --git a/spring-sample/build.gradle b/spring-sample/build.gradle index de59ebd..95f7145 100644 --- a/spring-sample/build.gradle +++ b/spring-sample/build.gradle @@ -3,7 +3,7 @@ version '1.0.0-SNAPSHOT' buildscript { ext { - springBootVersion = '2.1.4.RELEASE' + springBootVersion = '2.1.6.RELEASE' } repositories { jcenter() @@ -35,7 +35,7 @@ compileTestKotlin { } dependencies { - def askSdkVersion = '2.17.0' + def askSdkVersion = '2.19.0' implementation project(':core') implementation project(':ssml-builder')